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