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