logger: resolve level string in msgWithTime
[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     // set up options, program name, and facility for later calls to syslog(3)
131     if (_syslogEnabled)
132         openlog("quasselcore", LOG_PID, LOG_USER);
133 #endif
134
135     _initialized = true;
136
137     // Now that we've setup our logging backends, output pending messages
138     for (auto&& message : _messages) {
139         outputMessage(message);
140     }
141     if (!_keepMessages) {
142         _messages.clear();
143     }
144 }
145
146 void Logger::messageHandler(QtMsgType type, const QMessageLogContext&, const QString& message)
147 {
148     Quassel::instance()->logger()->handleMessage(type, message);
149 }
150
151 void Logger::handleMessage(QtMsgType type, const QString& msg)
152 {
153     switch (type) {
154     case QtDebugMsg:
155         handleMessage(LogLevel::Debug, msg);
156         break;
157     case QtInfoMsg:
158         handleMessage(LogLevel::Info, msg);
159         break;
160     case QtWarningMsg:
161         handleMessage(LogLevel::Warning, msg);
162         break;
163     case QtCriticalMsg:
164         handleMessage(LogLevel::Error, msg);
165         break;
166     case QtFatalMsg:
167         handleMessage(LogLevel::Fatal, msg);
168         break;
169     }
170 }
171
172 void Logger::handleMessage(LogLevel level, const QString& msg)
173 {
174     // Use signal connection to make this method thread-safe
175     emit messageLogged({QDateTime::currentDateTime(), level, msg});
176 }
177
178 void Logger::onMessageLogged(const LogEntry& message)
179 {
180     if (_keepMessages) {
181         _messages.push_back(message);
182     }
183
184     // If setup() wasn't called yet, just store the message - will be output later
185     if (_initialized) {
186         outputMessage(message);
187     }
188 }
189
190 void Logger::outputMessage(const LogEntry& message)
191 {
192     if (message.logLevel < _outputLevel) {
193         return;
194     }
195
196 #ifdef HAVE_SYSLOG
197     if (_syslogEnabled) {
198         int prio{LOG_INFO};
199         switch (message.logLevel) {
200         case LogLevel::Debug:
201             prio = LOG_DEBUG;
202             break;
203         case LogLevel::Info:
204             prio = LOG_INFO;
205             break;
206         case LogLevel::Warning:
207             prio = LOG_WARNING;
208             break;
209         case LogLevel::Error:
210             prio = LOG_ERR;
211             break;
212         case LogLevel::Fatal:
213             prio = LOG_CRIT;
214         }
215         syslog(prio, "%s", qPrintable(message.message));
216     }
217 #endif
218
219     if (!_logFile.fileName().isEmpty() || !_syslogEnabled) {
220         _logFile.write(msgWithTime(message));
221     }
222
223 #ifndef Q_OS_MAC
224     // For fatal messages, write log to dump file
225     if (message.logLevel == LogLevel::Fatal) {
226         QFile dumpFile{Quassel::instance()->coreDumpFileName()};
227         if (dumpFile.open(QIODevice::Append)) {
228             dumpFile.write(msgWithTime(message));
229             dumpFile.close();
230         }
231     }
232 #endif
233 }