]> vaikene.ee Git - evaf/blob - src/libs/Common/logger.cpp
Warning fixes and copyright update.
[evaf] / src / libs / Common / logger.cpp
1 /**
2 * @file Common/logger.cpp
3 * @brief iLogger interface implementation
4 * @author Enar Vaikene
5 *
6 * Copyright (c) 2011-2019 Enar Vaikene
7 *
8 * This file is part of the eVaf C++ cross-platform application development framework.
9 *
10 * This file can be used under the terms of the GNU General Public License
11 * version 3.0 as published by the Free Software Foundation and appearing in
12 * the file LICENSE included in the packaging of this file. Please review the
13 * the following information to ensure the GNU General Public License version
14 * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
15 *
16 * Alternatively, this file may be used in accordance with the Commercial License
17 * Agreement provided with the Software.
18 */
19
20 #include "logger.h"
21 #include "iregistry.h"
22 #include "iapp.h"
23 #include "iconfig.h"
24 #include "globals.h"
25 #include "inifile.h"
26 #include "version.h"
27
28 #include <QtCore>
29
30 #ifdef Q_OS_WIN32
31 # include <windows.h>
32 #endif
33
34 #ifdef Q_OS_LINUX
35 # include <stdio.h>
36 # include <stdarg.h>
37 # include <stdlib.h>
38 #endif
39
40
41 //-------------------------------------------------------------------
42
43 [[noreturn]] void eVaf::Common::Internal::defFatalMsgHandler(QString const & msg, QString const & source, QString const & where)
44 {
45 Q_UNUSED(source)
46
47 fprintf(stderr, "FATAL ERROR: %s (occurred in %s)\n", qPrintable(msg), qPrintable(where));
48
49 #ifdef Q_OS_LINUX
50 abort();
51 #else
52 exit(1);
53 #endif
54 }
55
56
57 //-------------------------------------------------------------------
58
59 using namespace eVaf::Common;
60
61 namespace
62 {
63 static Internal::Logger * singleton = nullptr;
64 }
65
66 iLogger * iLogger::instance()
67 {
68 if (nullptr == singleton)
69 {
70 singleton = new Internal::Logger;
71 }
72 return singleton;
73 }
74
75
76 //-------------------------------------------------------------------
77
78 using namespace eVaf::Common::Internal;
79
80 LoggerSource::LoggerSource()
81 : QSharedData()
82 , severity(iLogger::Fatal)
83 , maxSize(100 * 1024)
84 , maxCount(3)
85 {}
86
87 LoggerSource::LoggerSource(LoggerSource const & o)
88 : QSharedData()
89 , name(o.name)
90 , severity(o.severity)
91 , fileName(o.fileName)
92 , maxSize(o.maxSize)
93 , maxCount(o.maxCount)
94 {}
95
96 void LoggerSource::init(QString const & source)
97 {
98 name = source;
99 fileName = iApp::instance()->logDir() + source + ".log";
100
101 // Set default settings
102 severity = iLogger::Fatal;
103 maxSize = 100 * 1024;
104 maxCount = 3;
105
106 // Read settings from the 'logger.ini' file
107 QString confFileName = iApp::instance()->etcDir() + "logger.ini";
108 if (QFile::exists(confFileName)) {
109 IniFile ini(confFileName, QIODevice::ReadOnly);
110
111 // Default values for all sources
112 maxSize = 1024 * ini.getValue(".default/log_size", maxSize / 1024).toUInt();
113 maxCount = ini.getValue(".default/log_count", maxCount).toUInt();
114
115 // Default values for this source
116 maxSize = 1024 * ini.getValue(source.toLatin1() + "/log_size", maxSize / 1024).toUInt();
117 maxCount = ini.getValue(source.toLatin1() + "/log_count", maxCount).toUInt();
118 }
119 }
120
121
122 //-------------------------------------------------------------------
123
124 namespace
125 {
126 /// Recursively renames backup files
127 void renameBackupFile(QDir & dir, QString const & baseName, int idx)
128 {
129 QString f1 = QString("%1.%2").arg(baseName).arg(idx);
130 QString f2 = QString("%1.%2").arg(baseName).arg(idx + 1);
131
132 if (dir.exists(f2))
133 renameBackupFile(dir, baseName, idx + 1);
134
135 dir.rename(f1, f2);
136 }
137 }
138
139 void LoggerWorker::writeToLogFile(LoggerSource const & src, QString const & msg)
140 {
141 //::printf("writeToLogFile(\'%s\', \'%s\') fileName = \'%s\'\n", qPrintable(src.name), qPrintable(msg), qPrintable(src.fileName));
142 if (src.fileName.isEmpty())
143 return;
144 QFile f(src.fileName);
145 QFile::OpenMode mode;
146 #ifdef Q_OS_LINUX
147 mode = QFile::Append | QFile::Text | QFile::Unbuffered;
148 #else
149 mode = QFile::Append | QFile::Text;
150 #endif
151
152 // Open the log file
153 if (f.open(mode)) {
154
155 // Write to the log file and then close the file
156 f.write(msg.toLocal8Bit());
157 f.close();
158
159 // Check the file size
160 if (src.maxSize > 0 && f.size() > src.maxSize) {
161
162 QDir dir(QFileInfo(src.fileName).dir());
163 QString baseName = QFileInfo(src.fileName).fileName();
164
165 // Delete the oldest backup file if the number of files has reached the maximum
166 if (src.maxCount > 0 && dir.exists(QString("%1.%2").arg(baseName).arg(src.maxCount - 1)))
167 dir.remove(QString("%1.%2").arg(baseName).arg(src.maxCount - 1));
168
169 // Shift backup files (.0 -> .1, .1 -> .2 etc)
170 renameBackupFile(dir, baseName, 0);
171
172 // Rename the current log file to the backup #0 file
173 dir.rename(baseName, baseName + ".0");
174
175 }
176 }
177 }
178
179
180 //-------------------------------------------------------------------
181
182 void Logger::destroyInstance()
183 {
184 if (singleton != nullptr)
185 {
186 delete singleton;
187 singleton = nullptr;
188 }
189 }
190
191 Logger::Logger()
192 : iLogger()
193 , mReady(false)
194 , mFatalMsgHandler(defFatalMsgHandler)
195 , mConsoleSeverity(iLogger::Fatal)
196 {
197 setObjectName(QString("%1-iLogger").arg(VER_MODULE_NAME_STR));
198
199 qRegisterMetaType<LoggerSource>("LoggerSource");
200
201 // Create the default source
202 mDefaultSource = new LoggerSource;
203 mDefaultSource->name = "common";
204
205 write(Info, QString("%1 created").arg(objectName()), QString(), printf("%s:%s:%d", __FILE__, __FUNCTION__, __LINE__));
206 }
207
208 Logger::~Logger()
209 {
210 // Disconnect any potential receivers from this object
211 disconnect(this, SIGNAL(loggerEvent(Common::iLogger::Severity,QString,QString,QString)), nullptr, nullptr);
212
213 // Destroy the worker thread
214 if (mWorker) {
215 mWorker.reset();
216 if (mThread) {
217 mThread->quit();
218 mThread->wait();
219 mThread.reset();
220 }
221 }
222
223 write(Info, QString("%1 destroyed").arg(objectName()), QString(), printf("%s:%s:%d", __FILE__, __FUNCTION__, __LINE__));
224 }
225
226 bool Logger::init()
227 {
228 // Register our interface
229 iRegistry::instance()->registerInterface("iLogger", this);
230
231 // Clear existing sources in case the application was restarted
232 mSources.clear();
233
234 // Set the default source name to the name of the application
235 setDefaultSource(iApp::instance()->name());
236
237 // Read configuration parameters from the application's INI file
238 QVariant v = iConfig::instance()->getValue(QString("%1/general/log_level").arg(iApp::instance()->name()), severity());
239 if (v.isValid())
240 setSeverity(iLogger::Severity(qBound(int(iLogger::None), v.toInt(), int(iLogger::Debug))));
241 v = iConfig::instance()->getValue(QString("%1/general/log_size").arg(iApp::instance()->name()), maxSize());
242 if (v.isValid())
243 setMaxSize(v.toUInt());
244 v = iConfig::instance()->getValue(QString("%1/general/log_cnt").arg(iApp::instance()->name()), maxCount());
245 if (v.isValid())
246 setMaxCount(v.toUInt());
247
248 // Destroy the previous worker thread
249 if (mThread) {
250 mThread->quit();
251 mThread->wait();
252 }
253
254 // Create the worker thread
255 mWorker.reset(new LoggerWorker);
256 mThread.reset(new QThread);
257 mWorker->moveToThread(mThread.data());
258 mThread->start(QThread::IdlePriority);
259 connect(this, SIGNAL(writeToLogFile(LoggerSource,QString)), mWorker.data(), SLOT(writeToLogFile(LoggerSource,QString)), Qt::QueuedConnection);
260
261 mReady = true;
262
263 write(Info, QString("%1 initialized").arg(objectName()), QString(), printf("%s:%s:%d", __FILE__, __FUNCTION__, __LINE__));
264
265 return true;
266 }
267
268 QString Logger::defaultSource() const
269 {
270 return mDefaultSource->name;
271 }
272
273 void Logger::setDefaultSource(QString const & source)
274 {
275 LoggerSource * src = getSource();
276 if (src && src->name != source)
277 getSource(QString())->init(source);
278 }
279
280 iLogger::Severity Logger::severity(QString const & source)
281 {
282 return getSource(source)->severity;
283 }
284
285 void Logger::setSeverity(iLogger::Severity severity, QString const & source)
286 {
287 getSource(source)->severity = severity;
288 }
289
290 uint Logger::maxSize(QString const & source)
291 {
292 return getSource(source)->maxSize;
293 }
294
295 void Logger::setMaxSize(uint maxSize, QString const & source)
296 {
297 getSource(source)->maxSize = maxSize * 1024;
298 }
299
300 uint Logger::maxCount(QString const & source)
301 {
302 return getSource(source)->maxCount;
303 }
304
305 void Logger::setMaxCount(uint maxCount, QString const & source)
306 {
307 getSource(source)->maxCount = maxCount;
308 }
309
310 void Logger::setConsoleSeverity(iLogger::Severity severity)
311 {
312 mConsoleSeverity = severity;
313 }
314
315 void Logger::write(Severity severity, QString const & msg, QString const & source, QString const & where)
316 {
317 static char const * const severityText[] =
318 {
319 "[NONE] : ",
320 "[FATAL] : ",
321 "[ERROR] : ",
322 "[WARNING]: ",
323 "[INFO] : ",
324 "[DEBUG] : "
325 };
326
327 // Make sure that we don't output messages with the None severity
328 if (severity == iLogger::None)
329 return;
330
331 // Write to the log file
332 if (mReady) {
333 LoggerSource * src = getSource(source);
334 if (severity <= src->severity && src->severity != iLogger::None) {
335 QString buf;
336 QTextStream io(&buf);
337
338 // Date/time stamp
339 io << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
340
341 // Severity
342 io << " " << severityText[severity];
343
344 // Message
345 io << msg;
346
347 // Location in the source file
348 if (!where.isEmpty())
349 io << " (occurred in " << where << ")";
350
351 io << endl;
352 io.flush();
353
354 emit writeToLogFile(*src, buf);
355 }
356 }
357
358 // Output to the console
359 if (source.isEmpty() && severity <= mConsoleSeverity && mConsoleSeverity != iLogger::None) {
360 FILE * f = (severity < iLogger::Info) ? stderr : stdout;
361
362 // Set text colors
363 #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
364 switch (severity) {
365 case iLogger::Info:
366 fprintf(f, "\033[32m"); // Green
367 break;
368 case iLogger::Warning:
369 fprintf(f, "\033[1m"); // Bold
370 break;
371 case iLogger::Error:
372 fprintf(f, "\033[31m"); // Red
373 break;
374 case iLogger::Fatal:
375 fprintf(f, "\033[31m\033[1m"); // Bold Red
376 break;
377 default:
378 fprintf(f, "\033[34m"); // Blue
379 break;
380 }
381 #elif defined(Q_OS_WIN32)
382 switch (severity) {
383 case iLogger::Info:
384 setColor(FOREGROUND_GREEN);
385 break;
386 case iLogger::Warning:
387 setColor(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
388 break;
389 case iLogger::Error:
390 setColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
391 break;
392 case iLogger::Fatal:
393 setColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_RED);
394 break;
395 default:
396 setColor(FOREGROUND_BLUE);
397 break;
398 }
399 #endif
400
401 // Output the message
402 fprintf(f, "%s %s\n", severityText[severity], qPrintable(msg));
403
404 // Add the location
405 if (!where.isEmpty())
406 fprintf(f, "\t(occurred in %s)\n\n", qPrintable(where));
407
408 // Reset text colors
409 #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
410 fputs("\033[0m", f);
411 #elif defined(Q_OS_WIN32)
412 setColor(7);
413 #endif
414
415 // Flush the output
416 fflush(f);
417 }
418
419 // Inform others
420 emit loggerEvent(severity, msg, source, where);
421
422 // Handle fatal error messages
423 if (severity == iLogger::Fatal && mFatalMsgHandler)
424 mFatalMsgHandler(msg, source, where);
425 }
426
427 QString Logger::printf(char const * const fmt, ...) const
428 {
429 #ifdef Q_OS_WIN32
430 char str[4096];
431 #else
432 char * str = nullptr;
433 #endif
434
435 va_list ap;
436 #ifdef Q_OS_WIN32
437 va_start(ap, fmt);
438 # ifdef Q_CC_GNU
439 vsnprintf(str, sizeof(str), fmt, ap);
440 # else
441 _vsnprintf_s(str, sizeof(str), _TRUNCATE, fmt, ap);
442 # endif
443 va_end(ap);
444 #else
445 ::va_start(ap, fmt);
446 if (::vasprintf(&str, fmt, ap)) {} // IF is needed to avoid the compiler warning
447 ::va_end(ap);
448 #endif
449
450 QString rval(str);
451
452 #ifndef Q_OS_WIN32
453 ::free(str);
454 #endif
455
456 return rval;
457 }
458
459 QString Logger::printable(QByteArray const & msg) const
460 {
461 static char const * const ctrlChars[] = {
462 "NUL", "SOH", "STX", "ETX", "EOT", "ENW", "ACK", "BEL",
463 "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
464 "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
465 "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
466 };
467
468 QString rval;
469 int sz = msg.size();
470 for (int i = 0; i < sz; ++i) {
471 uchar ch = uchar(msg.at(i));
472 if (ch < 32)
473 rval.append(QString("[%1]").arg(ctrlChars[ch]));
474 else if (ch < 127)
475 rval.append(msg.at(i));
476 else if (ch == 127)
477 rval.append("[DEL]");
478 else
479 rval.append(QString("[\\x%1]").arg(ch, 2, 16, QChar('0')));
480 }
481
482 return rval;
483 }
484
485 FatalMsgHandler Logger::installFatalMsgHandler(FatalMsgHandler newHandler)
486 {
487 FatalMsgHandler oldHandler = mFatalMsgHandler;
488 mFatalMsgHandler = newHandler;
489 return oldHandler;
490 }
491
492 LoggerSource * Logger::getSource(QString const & source)
493 {
494 if (source.isEmpty() || source == mDefaultSource->name)
495 return mDefaultSource.data();
496
497 QHash<QString, QExplicitlySharedDataPointer<LoggerSource> >::const_iterator it = mSources.constFind(source);
498 if (it != mSources.constEnd())
499 return it->data();
500 else {
501 // Create the new source
502 QExplicitlySharedDataPointer<LoggerSource> src(new LoggerSource);
503 mSources.insert(source, src);
504
505 // Initialize the new source
506 src->init(source);
507
508 return src.data();
509 }
510 }
511
512 #ifdef Q_OS_WIN32
513 void Logger::setColor(short int c)
514 {
515 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
516 SetConsoleTextAttribute(handle, c);
517 }
518 #endif