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