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