logger: resolve program name using RunMode and BuildInfo
[quassel.git] / src / common / logger.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <iostream>
22
23 #ifdef HAVE_SYSLOG
24 #    include <syslog.h>
25 #endif
26
27 #include <QByteArray>
28 #include <QDateTime>
29 #include <QDebug>
30 #include <QFile>
31
32 #include "logger.h"
33 #include "quassel.h"
34 #include "types.h"
35
36 namespace {
37
38 QByteArray msgWithTime(const Logger::LogEntry& msg)
39 {
40     QString levelString;
41
42     switch (msg.logLevel) {
43         case Logger::LogLevel::Debug:
44             levelString = "[Debug] ";
45             break;
46         case Logger::LogLevel::Info:
47             levelString = "[Info ] ";
48             break;
49         case Logger::LogLevel::Warning:
50             levelString = "[Warn ] ";
51             break;
52         case Logger::LogLevel::Error:
53             levelString = "[Error] ";
54             break;
55         case Logger::LogLevel::Fatal:
56             levelString = "[FATAL] ";
57             break;
58     }
59
60     return (msg.timeStamp.toString("yyyy-MM-dd hh:mm:ss ") + levelString + msg.message + "\n").toUtf8();
61 }
62
63 }  // namespace
64
65 Logger::Logger(QObject* parent)
66     : QObject(parent)
67 {
68     static bool registered = []() {
69         qRegisterMetaType<LogEntry>();
70         return true;
71     }();
72     Q_UNUSED(registered)
73
74     connect(this, &Logger::messageLogged, this, &Logger::onMessageLogged);
75
76     qInstallMessageHandler(Logger::messageHandler);
77 }
78
79 Logger::~Logger()
80 {
81     // If we're not initialized yet, output pending messages so they don't get lost
82     if (!_initialized) {
83         for (auto&& message : _messages) {
84             std::cerr << msgWithTime(message).constData();
85         }
86     }
87 }
88
89 std::vector<Logger::LogEntry> Logger::messages() const
90 {
91     return _messages;
92 }
93
94 void Logger::setup(bool keepMessages)
95 {
96     _keepMessages = keepMessages;
97
98     // Set maximum level for output (we still store/announce all messages for client-side filtering)
99     if (Quassel::isOptionSet("loglevel")) {
100         QString level = Quassel::optionValue("loglevel").toLower();
101         if (level == "debug")
102             _outputLevel = LogLevel::Debug;
103         else if (level == "info")
104             _outputLevel = LogLevel::Info;
105         else if (level == "warning")
106             _outputLevel = LogLevel::Warning;
107         else if (level == "error")
108             _outputLevel = LogLevel::Error;
109         else {
110             throw ExitException{EXIT_FAILURE, qPrintable(tr("Invalid log level %1; supported are Debug|Info|Warning|Error").arg(level))};
111         }
112     }
113
114     QString logfilename = Quassel::optionValue("logfile");
115     if (!logfilename.isEmpty()) {
116         _logFile.setFileName(logfilename);
117         if (!_logFile.open(QFile::Append | QFile::Unbuffered | QFile::Text)) {
118             qCritical() << qPrintable(tr("Could not open log file \"%1\": %2").arg(logfilename, _logFile.errorString()));
119         }
120     }
121     if (!_logFile.isOpen()) {
122         if (!_logFile.open(stderr, QFile::WriteOnly | QFile::Unbuffered | QFile::Text)) {
123             qCritical() << qPrintable(tr("Cannot write to stderr: %1").arg(_logFile.errorString()));
124         }
125     }
126
127 #ifdef HAVE_SYSLOG
128     _syslogEnabled = Quassel::isOptionSet("syslog");
129
130     Quassel::RunMode mode = Quassel::runMode();
131     Quassel::BuildInfo info = Quassel::buildInfo();
132     QString prgname = info.applicationName;
133
134     if (mode == Quassel::RunMode::ClientOnly) {
135         prgname = info.clientApplicationName;
136     } else if (mode == Quassel::RunMode::CoreOnly) {
137         prgname = info.coreApplicationName;
138     }
139
140     _prgname = prgname.toLocal8Bit();
141
142     // set up options, program name, and facility for later calls to syslog(3)
143     if (_syslogEnabled) {
144         openlog(_prgname.constData(), LOG_PID, LOG_USER);
145     }
146 #endif
147
148     _initialized = true;
149
150     // Now that we've setup our logging backends, output pending messages
151     for (auto&& message : _messages) {
152         outputMessage(message);
153     }
154     if (!_keepMessages) {
155         _messages.clear();
156     }
157 }
158
159 void Logger::messageHandler(QtMsgType type, const QMessageLogContext&, const QString& message)
160 {
161     Quassel::instance()->logger()->handleMessage(type, message);
162 }
163
164 void Logger::handleMessage(QtMsgType type, const QString& msg)
165 {
166     switch (type) {
167     case QtDebugMsg:
168         handleMessage(LogLevel::Debug, msg);
169         break;
170     case QtInfoMsg:
171         handleMessage(LogLevel::Info, msg);
172         break;
173     case QtWarningMsg:
174         handleMessage(LogLevel::Warning, msg);
175         break;
176     case QtCriticalMsg:
177         handleMessage(LogLevel::Error, msg);
178         break;
179     case QtFatalMsg:
180         handleMessage(LogLevel::Fatal, msg);
181         break;
182     }
183 }
184
185 void Logger::handleMessage(LogLevel level, const QString& msg)
186 {
187     // Use signal connection to make this method thread-safe
188     emit messageLogged({QDateTime::currentDateTime(), level, msg});
189 }
190
191 void Logger::onMessageLogged(const LogEntry& message)
192 {
193     if (_keepMessages) {
194         _messages.push_back(message);
195     }
196
197     // If setup() wasn't called yet, just store the message - will be output later
198     if (_initialized) {
199         outputMessage(message);
200     }
201 }
202
203 void Logger::outputMessage(const LogEntry& message)
204 {
205     if (message.logLevel < _outputLevel) {
206         return;
207     }
208
209 #ifdef HAVE_SYSLOG
210     if (_syslogEnabled) {
211         int prio{LOG_INFO};
212         switch (message.logLevel) {
213         case LogLevel::Debug:
214             prio = LOG_DEBUG;
215             break;
216         case LogLevel::Info:
217             prio = LOG_INFO;
218             break;
219         case LogLevel::Warning:
220             prio = LOG_WARNING;
221             break;
222         case LogLevel::Error:
223             prio = LOG_ERR;
224             break;
225         case LogLevel::Fatal:
226             prio = LOG_CRIT;
227         }
228         syslog(prio, "%s", qPrintable(message.message));
229     }
230 #endif
231
232     if (!_logFile.fileName().isEmpty() || !_syslogEnabled) {
233         _logFile.write(msgWithTime(message));
234     }
235
236 #ifndef Q_OS_MAC
237     // For fatal messages, write log to dump file
238     if (message.logLevel == LogLevel::Fatal) {
239         QFile dumpFile{Quassel::instance()->coreDumpFileName()};
240         if (dumpFile.open(QIODevice::Append)) {
241             dumpFile.write(msgWithTime(message));
242             dumpFile.close();
243         }
244     }
245 #endif
246 }