1 #include <QDebug>
2 #include <QMetaMethod>
3 #include <QMessageBox>
4 #include <cstdio>
5 #include <cstdlib>
6 #include "logger.h"
7
8
9 static bool loggerInstanceSetBefore = false;
10 static Logger *loggerInstance = nullptr;
11
loggerCallback(QtMsgType type,const QMessageLogContext & context,const QString & msg)12 void loggerCallback(QtMsgType type, const QMessageLogContext &context, const QString &msg)
13 {
14 Q_UNUSED(context)
15 switch (type) {
16 case QtDebugMsg:
17 Logger::log("qt", "debug", msg);
18 break;
19 case QtInfoMsg:
20 Logger::log("qt", "info", msg);
21 break;
22 case QtWarningMsg:
23 Logger::log("qt", "warn", msg);
24 break;
25 case QtCriticalMsg:
26 Logger::log("qt", "crit", msg);
27 break;
28 case QtFatalMsg:
29 Logger::fatalMessage();
30 fprintf(stderr, "[FATALITY] [qt] fatal: %s\n", msg.toUtf8().data());
31 std::abort();
32 }
33 }
34
Logger(QObject * owner)35 Logger::Logger(QObject *owner) : QObject(owner)
36 {
37 if (loggerInstanceSetBefore)
38 std::abort();
39 qInstallMessageHandler(loggerCallback);
40 immediateMode = false;
41 flushTimer = new QTimer(this);
42 connect(flushTimer, &QTimer::timeout,
43 this, &Logger::flushMessages);
44 elapsed.start();
45 }
46
~Logger()47 Logger::~Logger()
48 {
49 qInstallMessageHandler(nullptr);
50 if (logFileStream) {
51 delete logFileStream;
52 logFileStream = nullptr;
53 }
54 if (logFile) {
55 delete logFile;
56 logFile = nullptr;
57 }
58 loggerInstance = nullptr;
59 }
60
singleton()61 Logger *Logger::singleton()
62 {
63 // Only create an instance if it hasn't done so before
64 if (!loggerInstanceSetBefore && !loggerInstance) {
65 loggerInstance = new Logger();
66 loggerInstanceSetBefore = true;
67 }
68 return loggerInstance;
69 }
70
71 // The log buffer class has likely been moved to
72 // its own separate thread, so remotely invoke
73 // the respective log methods.
log(QString line)74 void Logger::log(QString line)
75 {
76 Logger *log = singleton();
77 if (!log)
78 return;
79 QMetaObject::invokeMethod(log, "makeLog",
80 Qt::QueuedConnection,
81 Q_ARG(QString, line));
82 }
83
log(QString prefix,QString message)84 void Logger::log(QString prefix, QString message)
85 {
86 Logger *log = singleton();
87 if (!log)
88 return;
89 QMetaObject::invokeMethod(log, "makeLogPrefixed",
90 Qt::QueuedConnection,
91 Q_ARG(QString, prefix),
92 Q_ARG(QString, message));
93 }
94
log(QString prefix,QString level,QString message)95 void Logger::log(QString prefix, QString level, QString message)
96 {
97 Logger *log = singleton();
98 if (!log)
99 return;
100 QMetaObject::invokeMethod(log, "makeLogDescriptively",
101 Qt::QueuedConnection,
102 Q_ARG(QString, prefix),
103 Q_ARG(QString, level),
104 Q_ARG(QString, message));
105 }
106
logs(const QStringList & strings)107 void Logger::logs(const QStringList &strings)
108 {
109 log(strings.join(' '));
110 }
111
logs(QString prefix,const QStringList & strings)112 void Logger::logs(QString prefix, const QStringList &strings)
113 {
114 log(prefix, strings.join(' '));
115 }
116
logs(QString prefix,QString level,const QStringList & strings)117 void Logger::logs(QString prefix, QString level, const QStringList &strings)
118 {
119 log(prefix, level, strings.join(' '));
120 }
121
fatalMessage()122 void Logger::fatalMessage()
123 {
124 // Oops! Something went very wrong!
125 // Try to flush anything pending to stderr and abort
126 if (loggerInstance) {
127 for (const auto &i : qAsConst(loggerInstance->pendingMessages))
128 std::fprintf(stderr, "%s\n", i.toLocal8Bit().data());
129 }
130 }
131
setLogFile(QString fileName)132 void Logger::setLogFile(QString fileName)
133 {
134 if (logFileName == fileName)
135 return;
136 logFileName = fileName;
137 if (fileName.isEmpty()) {
138 log("logger", "log file closed");
139 if (logFileStream) {
140 delete logFileStream;
141 logFileStream = nullptr;
142 }
143 if (logFile) {
144 delete logFile;
145 logFile = nullptr;
146 }
147 return;
148 }
149 logFile = new QFile(fileName);
150 if (!logFile->open(QFile::WriteOnly))
151 return;
152
153 logs("logger", {"log file", logFileName, "opened for writing"});
154 logFileStream = new QTextStream(logFile);
155 logFileStream->setCodec("UTF-8");
156 logFileStream->setGenerateByteOrderMark(true);
157 }
158
setLoggingEnabled(bool enabled)159 void Logger::setLoggingEnabled(bool enabled)
160 {
161 if (enabled && !loggingEnabled) {
162 loggingEnabled = true;
163 makeLogPrefixed("logger", "enabling logging");
164 if (!immediateMode)
165 flushTimer->start();
166 } else if (!enabled && loggingEnabled) {
167 makeLogPrefixed("logger", "disabling logging");
168 flushMessages();
169 flushTimer->stop();
170 loggingEnabled = false;
171 }
172 }
173
setFlushTime(int msec)174 void Logger::setFlushTime(int msec)
175 {
176 if (msec <= 0) {
177 flushTimer->stop();
178 if (!immediateMode)
179 flushMessages();
180 immediateMode = true;
181 return;
182 } else {
183 immediateMode = false;
184 flushTimer->setInterval(std::max(100, msec));
185 flushTimer->start();
186 }
187 }
188
flushMessages()189 void Logger::flushMessages()
190 {
191 if (pendingMessages.isEmpty())
192 return;
193 if (logFileStream) {
194 *logFileStream << pendingMessages.join('\n') << '\n';
195 logFileStream->flush();
196 }
197 emit logMessageBuffer(pendingMessages);
198 pendingMessages.clear();
199 }
200
makeLog(QString line)201 void Logger::makeLog(QString line)
202 {
203 if (!loggingEnabled)
204 return;
205 line = QString("[%1] %2").arg(QString::number(elapsed.nsecsElapsed()/1000000000.0, 'f', 9), line.trimmed());
206 // If you're encountering early or fantastic errors, uncomment this line:
207 //fprintf(stderr, "%s\n", line.toLocal8Bit().constData());
208 if (immediateMode) {
209 emit logMessage(line);
210 if (logFileStream) {
211 *logFileStream << line << '\n';
212 logFileStream->flush();
213 }
214 } else {
215 pendingMessages.append(line);
216 }
217 }
218
makeLogPrefixed(QString prefix,QString message)219 void Logger::makeLogPrefixed(QString prefix, QString message)
220 {
221 makeLog(QString("[%1] %2").arg(prefix, message));
222 }
223
makeLogDescriptively(QString prefix,QString level,QString message)224 void Logger::makeLogDescriptively(QString prefix, QString level, QString message)
225 {
226 makeLog(QString("[%1] %2: %3").arg(prefix, level, message));
227 }
228
229
230
LogStream(QString prefix,QString level)231 LogStream::LogStream(QString prefix, QString level) : buffer(),
232 prefix(prefix), level(level), stream(&buffer)
233 {
234
235 }
236
~LogStream()237 LogStream::~LogStream()
238 {
239 if (buffer.isEmpty())
240 return;
241 if (directFlush) {
242 fprintf(stderr, "[%s] %s: %s\n",
243 prefix.toUtf8().data(),
244 level.toUtf8().data(),
245 buffer.toUtf8().data());
246 return;
247 }
248 if (prefix.isEmpty() && level.isEmpty())
249 Logger::log(buffer);
250 else if (level.isEmpty())
251 Logger::log(prefix, buffer);
252 else
253 Logger::log(prefix, level, buffer);
254 }
255
always()256 LogStream &LogStream::always()
257 {
258 directFlush = true;
259 return *this;
260 }
261
operator <<(const char * a)262 LogStream &LogStream::operator<<(const char *a)
263 {
264 stream << a;
265 return *this;
266 }
267
operator <<(const QString & a)268 LogStream &LogStream::operator<<(const QString &a)
269 {
270 stream << a;
271 return *this;
272 }
273
operator <<(const QVariant & a)274 LogStream &LogStream::operator<<(const QVariant &a)
275 {
276 if (a.canConvert(QMetaType::QVariantMap)) {
277 stream << "{";
278 auto list = a.toMap();
279 int count = list.count();
280 for (auto it = list.constBegin(); it != list.constEnd(); it++, count--) {
281 stream << '"' << it.key() << "\":";
282 *this << it.value();
283 if (count > 1)
284 stream << ", ";
285 }
286 *this << "}";
287 } else if (a.canConvert(QMetaType::QVariantList)) {
288 stream << "[";
289 auto list = a.toList();
290 int count = list.count();
291 for (int i = 0; i < count; i++) {
292 *this << list.value(i);
293 if (i < count - 1)
294 stream << ", ";
295 }
296 stream << "]";
297 } else if (a.canConvert(QMetaType::QString)) {
298 stream << a.toString();
299 } else {
300 stream << "(unserializable)";
301 }
302 return *this;
303 }
304