]> vaikene.ee Git - evaf/blob - src/libs/Common/logger.cpp
Fixed small mistakes detected with clang++
[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 _vsnprintf_s(str, sizeof(str), _TRUNCATE, fmt, ap);
425 va_end(ap);
426 #else
427 ::va_start(ap, fmt);
428 if (::vasprintf(&str, fmt, ap)) {}; // IF is needed to avoid the compiler warning
429 ::va_end(ap);
430 #endif
431
432 QString rval(str);
433
434 #ifndef Q_OS_WIN32
435 ::free(str);
436 #endif
437
438 return rval;
439 }
440
441 QString Logger::printable(QByteArray const & msg) const
442 {
443 static char const * const ctrlChars[] = {
444 "NUL", "SOH", "STX", "ETX", "EOT", "ENW", "ACK", "BEL",
445 "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
446 "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
447 "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
448 };
449
450 QString rval;
451 int sz = msg.size();
452 for (int i = 0; i < sz; ++i) {
453 uchar ch = uchar(msg.at(i));
454 if (ch < 32)
455 rval.append(QString("[%1]").arg(ctrlChars[ch]));
456 else if (ch < 127)
457 rval.append(msg.at(i));
458 else if (ch == 127)
459 rval.append("[DEL]");
460 else
461 rval.append(QString("[\\x%1]").arg(ch, 2, 16, QChar('0')));
462 }
463
464 return rval;
465 }
466
467 FatalMsgHandler Logger::installFatalMsgHandler(FatalMsgHandler newHandler)
468 {
469 FatalMsgHandler oldHandler = mFatalMsgHandler;
470 mFatalMsgHandler = newHandler;
471 return oldHandler;
472 }
473
474 LoggerSource * Logger::getSource(QString const & source)
475 {
476 if (source.isEmpty() || source == mDefaultSource->name)
477 return mDefaultSource.data();
478
479 QHash<QString, QExplicitlySharedDataPointer<LoggerSource> >::const_iterator it = mSources.constFind(source);
480 if (it != mSources.constEnd())
481 return it->data();
482 else {
483 // Create the new source
484 QExplicitlySharedDataPointer<LoggerSource> src(new LoggerSource);
485 mSources.insert(source, src);
486
487 // Initialize the new source
488 src->init(source);
489
490 return src.data();
491 }
492 }
493
494 #ifdef Q_OS_WIN32
495 void Logger::setColor(short int c)
496 {
497 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
498 SetConsoleTextAttribute(handle, c);
499 }
500 #endif