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