logger: Make output to syslog nicer
[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
35 namespace {
36
37 QByteArray msgWithTime(const Logger::LogEntry &msg)
38 {
39     QString levelString;
40
41     switch (msg.logLevel) {
42         case Logger::LogLevel::Debug:
43             levelString = "[Debug] ";
44             break;
45         case Logger::LogLevel::Info:
46             levelString = "[Info ] ";
47             break;
48         case Logger::LogLevel::Warning:
49             levelString = "[Warn ] ";
50             break;
51         case Logger::LogLevel::Error:
52             levelString = "[Error] ";
53             break;
54         case Logger::LogLevel::Fatal:
55             levelString = "[FATAL] ";
56             break;
57     }
58
59     return (msg.timeStamp.toString("yyyy-MM-dd hh:mm:ss ") + levelString + msg.message + "\n").toUtf8();
60 };
61
62 }
63
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, SIGNAL(messageLogged(Logger::LogEntry)), this, SLOT(onMessageLogged(Logger::LogEntry)));
75
76 #if QT_VERSION < 0x050000
77     qInstallMsgHandler(Logger::messageHandler);
78 #else
79     qInstallMessageHandler(Logger::messageHandler);
80 #endif
81 }
82
83
84 Logger::~Logger()
85 {
86     // If we're not initialized yet, output pending messages so they don't get lost
87     if (!_initialized) {
88         for (auto &&message : _messages) {
89             std::cerr << msgWithTime(message).constData();
90         }
91     }
92 }
93
94
95 std::vector<Logger::LogEntry> Logger::messages() const
96 {
97     return _messages;
98 }
99
100
101 bool Logger::setup(bool keepMessages)
102 {
103     _keepMessages = keepMessages;
104
105     // Set maximum level for output (we still store/announce all messages for client-side filtering)
106     if (Quassel::isOptionSet("loglevel")) {
107         QString level = Quassel::optionValue("loglevel").toLower();
108         if (level == "debug")
109             _outputLevel = LogLevel::Debug;
110         else if (level == "info")
111             _outputLevel = LogLevel::Info;
112         else if (level == "warning")
113             _outputLevel = LogLevel::Warning;
114         else if (level == "error")
115             _outputLevel = LogLevel::Error;
116         else {
117             qCritical() << qPrintable(tr("Invalid log level %1; supported are Debug|Info|Warning|Error").arg(level));
118             return false;
119         }
120     }
121
122     QString logfilename = Quassel::optionValue("logfile");
123     if (!logfilename.isEmpty()) {
124         _logFile.setFileName(logfilename);
125         if (!_logFile.open(QFile::Append|QFile::Unbuffered|QFile::Text)) {
126             qCritical() << qPrintable(tr("Could not open log file \"%1\": %2").arg(logfilename, _logFile.errorString()));
127         }
128     }
129     if (!_logFile.isOpen()) {
130         if (!_logFile.open(stderr, QFile::WriteOnly|QFile::Unbuffered|QFile::Text)) {
131             qCritical() << qPrintable(tr("Cannot write to stderr: %1").arg(_logFile.errorString()));
132         }
133     }
134
135 #ifdef HAVE_SYSLOG
136     _syslogEnabled = Quassel::isOptionSet("syslog");
137
138     Quassel::RunMode mode = Quassel::runMode();
139     Quassel::BuildInfo info = Quassel::buildInfo();
140     QString prgname = info.applicationName;
141
142     if (mode == Quassel::RunMode::ClientOnly) {
143         prgname = info.clientApplicationName;
144     } else if (mode == Quassel::RunMode::CoreOnly) {
145         prgname = info.coreApplicationName;
146     }
147
148     _prgname = prgname.toLocal8Bit();
149
150     // set up options, program name, and facility for later calls to syslog(3)
151     if (_syslogEnabled) {
152         openlog(_prgname.constData(), LOG_PID, LOG_USER);
153     }
154 #endif
155
156     _initialized = true;
157
158     // Now that we've setup our logging backends, output pending messages
159     for (auto &&message : _messages) {
160         outputMessage(message);
161     }
162     if (!_keepMessages) {
163         _messages.clear();
164     }
165
166     return true;
167 }
168
169
170 #if QT_VERSION < 0x050000
171 void Logger::messageHandler(QtMsgType type, const char *message)
172 #else
173 void Logger::messageHandler(QtMsgType type, const QMessageLogContext &, const QString &message)
174 #endif
175 {
176     Quassel::instance()->logger()->handleMessage(type, message);
177 }
178
179
180 void Logger::handleMessage(QtMsgType type, const QString &msg)
181 {
182     switch (type) {
183     case QtDebugMsg:
184         handleMessage(LogLevel::Debug, msg);
185         break;
186 #if QT_VERSION >= 0x050500
187     case QtInfoMsg:
188         handleMessage(LogLevel::Info, msg);
189         break;
190 #endif
191     case QtWarningMsg:
192         handleMessage(LogLevel::Warning, msg);
193         break;
194     case QtCriticalMsg:
195         handleMessage(LogLevel::Error, msg);
196         break;
197     case QtFatalMsg:
198         handleMessage(LogLevel::Fatal, msg);
199         break;
200     }
201 }
202
203
204 void Logger::handleMessage(LogLevel level, const QString &msg)
205 {
206     // Use signal connection to make this method thread-safe
207     emit messageLogged({QDateTime::currentDateTime(), level, msg});
208 }
209
210
211 void Logger::onMessageLogged(const LogEntry &message)
212 {
213     if (_keepMessages) {
214         _messages.push_back(message);
215     }
216
217     // If setup() wasn't called yet, just store the message - will be output later
218     if (_initialized) {
219         outputMessage(message);
220     }
221 }
222
223
224 void Logger::outputMessage(const LogEntry &message)
225 {
226     if (message.logLevel < _outputLevel) {
227         return;
228     }
229
230 #ifdef HAVE_SYSLOG
231     if (_syslogEnabled) {
232         int prio{LOG_INFO};
233         switch (message.logLevel) {
234         case LogLevel::Debug:
235             prio = LOG_DEBUG;
236             break;
237         case LogLevel::Info:
238             prio = LOG_INFO;
239             break;
240         case LogLevel::Warning:
241             prio = LOG_WARNING;
242             break;
243         case LogLevel::Error:
244             prio = LOG_ERR;
245             break;
246         case LogLevel::Fatal:
247             prio = LOG_CRIT;
248         }
249         syslog(prio, "%s", qPrintable(message.message));
250     }
251 #endif
252
253     if (!_logFile.fileName().isEmpty() || !_syslogEnabled) {
254         _logFile.write(msgWithTime(message));
255     }
256
257 #ifndef Q_OS_MAC
258     // For fatal messages, write log to dump file
259     if (message.logLevel == LogLevel::Fatal) {
260         QFile dumpFile{Quassel::instance()->coreDumpFileName()};
261         if (dumpFile.open(QIODevice::Append)) {
262             dumpFile.write(msgWithTime(message));
263             dumpFile.close();
264         }
265     }
266 #endif
267
268 }