modernize: Pass arguments by value and move in constructors
[quassel.git] / src / common / logger.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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     return (msg.timeStamp.toString("yyyy-MM-dd hh:mm:ss ") + msg.message + "\n").toUtf8();
41 }
42
43 }
44
45
46 Logger::Logger(QObject *parent)
47     : QObject(parent)
48 {
49     static bool registered = []() {
50         qRegisterMetaType<LogEntry>();
51         return true;
52     }();
53     Q_UNUSED(registered)
54
55     connect(this, SIGNAL(messageLogged(Logger::LogEntry)), this, SLOT(onMessageLogged(Logger::LogEntry)));
56
57     qInstallMessageHandler(Logger::messageHandler);
58 }
59
60
61 Logger::~Logger()
62 {
63     // If we're not initialized yet, output pending messages so they don't get lost
64     if (!_initialized) {
65         for (auto &&message : _messages) {
66             std::cerr << msgWithTime(message).constData();
67         }
68     }
69 }
70
71
72 std::vector<Logger::LogEntry> Logger::messages() const
73 {
74     return _messages;
75 }
76
77
78 void Logger::setup(bool keepMessages)
79 {
80     _keepMessages = keepMessages;
81
82     // Set maximum level for output (we still store/announce all messages for client-side filtering)
83     if (Quassel::isOptionSet("loglevel")) {
84         QString level = Quassel::optionValue("loglevel").toLower();
85         if (level == "debug")
86             _outputLevel = LogLevel::Debug;
87         else if (level == "info")
88             _outputLevel = LogLevel::Info;
89         else if (level == "warning")
90             _outputLevel = LogLevel::Warning;
91         else if (level == "error")
92             _outputLevel = LogLevel::Error;
93         else {
94             throw ExitException{EXIT_FAILURE, qPrintable(tr("Invalid log level %1; supported are Debug|Info|Warning|Error").arg(level))};
95         }
96     }
97
98     QString logfilename = Quassel::optionValue("logfile");
99     if (!logfilename.isEmpty()) {
100         _logFile.setFileName(logfilename);
101         if (!_logFile.open(QFile::Append|QFile::Unbuffered|QFile::Text)) {
102             qCritical() << qPrintable(tr("Could not open log file \"%1\": %2").arg(logfilename, _logFile.errorString()));
103         }
104     }
105     if (!_logFile.isOpen()) {
106         if (!_logFile.open(stderr, QFile::WriteOnly|QFile::Unbuffered|QFile::Text)) {
107             qCritical() << qPrintable(tr("Cannot write to stderr: %1").arg(_logFile.errorString()));
108         }
109     }
110
111 #ifdef HAVE_SYSLOG
112     _syslogEnabled = Quassel::isOptionSet("syslog");
113 #endif
114
115     _initialized = true;
116
117     // Now that we've setup our logging backends, output pending messages
118     for (auto &&message : _messages) {
119         outputMessage(message);
120     }
121     if (!_keepMessages) {
122         _messages.clear();
123     }
124 }
125
126
127 void Logger::messageHandler(QtMsgType type, const QMessageLogContext &, const QString &message)
128 {
129     Quassel::instance()->logger()->handleMessage(type, message);
130 }
131
132
133 void Logger::handleMessage(QtMsgType type, const QString &msg)
134 {
135     switch (type) {
136     case QtDebugMsg:
137         handleMessage(LogLevel::Debug, msg);
138         break;
139     case QtInfoMsg:
140         handleMessage(LogLevel::Info, msg);
141         break;
142     case QtWarningMsg:
143         handleMessage(LogLevel::Warning, msg);
144         break;
145     case QtCriticalMsg:
146         handleMessage(LogLevel::Error, msg);
147         break;
148     case QtFatalMsg:
149         handleMessage(LogLevel::Fatal, msg);
150         break;
151     }
152 }
153
154
155 void Logger::handleMessage(LogLevel level, const QString &msg)
156 {
157     QString logString;
158
159     switch (level) {
160     case LogLevel::Debug:
161         logString = "[Debug] ";
162         break;
163     case LogLevel::Info:
164         logString = "[Info ] ";
165         break;
166     case LogLevel::Warning:
167         logString = "[Warn ] ";
168         break;
169     case LogLevel::Error:
170         logString = "[Error] ";
171         break;
172     case LogLevel::Fatal:
173         logString = "[FATAL] ";
174         break;
175     }
176
177     // Use signal connection to make this method thread-safe
178     emit messageLogged({QDateTime::currentDateTime(), level, logString += msg});
179 }
180
181
182 void Logger::onMessageLogged(const LogEntry &message)
183 {
184     if (_keepMessages) {
185         _messages.push_back(message);
186     }
187
188     // If setup() wasn't called yet, just store the message - will be output later
189     if (_initialized) {
190         outputMessage(message);
191     }
192 }
193
194
195 void Logger::outputMessage(const LogEntry &message)
196 {
197     if (message.logLevel < _outputLevel) {
198         return;
199     }
200
201 #ifdef HAVE_SYSLOG
202     if (_syslogEnabled) {
203         int prio{LOG_INFO};
204         switch (message.logLevel) {
205         case LogLevel::Debug:
206             prio = LOG_DEBUG;
207             break;
208         case LogLevel::Info:
209             prio = LOG_INFO;
210             break;
211         case LogLevel::Warning:
212             prio = LOG_WARNING;
213             break;
214         case LogLevel::Error:
215             prio = LOG_ERR;
216             break;
217         case LogLevel::Fatal:
218             prio = LOG_CRIT;
219         }
220         syslog(prio|LOG_USER, "%s", qPrintable(message.message));
221     }
222 #endif
223
224     if (!_logFile.fileName().isEmpty() || !_syslogEnabled) {
225         _logFile.write(msgWithTime(message));
226     }
227
228 #ifndef Q_OS_MAC
229     // For fatal messages, write log to dump file
230     if (message.logLevel == LogLevel::Fatal) {
231         QFile dumpFile{Quassel::instance()->coreDumpFileName()};
232         if (dumpFile.open(QIODevice::Append)) {
233             dumpFile.write(msgWithTime(message));
234             dumpFile.close();
235         }
236     }
237 #endif
238
239 }