2 * @file Common/logger.cpp
3 * @brief iLogger interface implementation
6 * Copyright (c) 2011-2019 Enar Vaikene
8 * This file is part of the eVaf C++ cross-platform application development framework.
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.
16 * Alternatively, this file may be used in accordance with the Commercial License
17 * Agreement provided with the Software.
21 #include "iregistry.h"
41 //-------------------------------------------------------------------
43 [[noreturn]] void eVaf::Common::Internal::defFatalMsgHandler(QString
const & msg
, QString
const & source
, QString
const & where
)
47 fprintf(stderr
, "FATAL ERROR: %s (occurred in %s)\n", qPrintable(msg
), qPrintable(where
));
57 //-------------------------------------------------------------------
59 using namespace eVaf::Common
;
63 static Internal::Logger
* singleton
= nullptr;
66 iLogger
* iLogger::instance()
68 if (nullptr == singleton
)
70 singleton
= new Internal::Logger
;
76 //-------------------------------------------------------------------
78 using namespace eVaf::Common::Internal
;
80 LoggerSource::LoggerSource()
82 , severity(iLogger::Fatal
)
87 LoggerSource::LoggerSource(LoggerSource
const & o
)
90 , severity(o
.severity
)
91 , fileName(o
.fileName
)
93 , maxCount(o
.maxCount
)
96 void LoggerSource::init(QString
const & source
)
99 fileName
= iApp::instance()->logDir() + source
+ ".log";
101 // Set default settings
102 severity
= iLogger::Fatal
;
103 maxSize
= 100 * 1024;
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
);
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();
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();
122 //-------------------------------------------------------------------
126 /// Recursively renames backup files
127 void renameBackupFile(QDir
& dir
, QString
const & baseName
, int idx
)
129 QString f1
= QString("%1.%2").arg(baseName
).arg(idx
);
130 QString f2
= QString("%1.%2").arg(baseName
).arg(idx
+ 1);
133 renameBackupFile(dir
, baseName
, idx
+ 1);
139 void LoggerWorker::writeToLogFile(LoggerSource
const & src
, QString
const & msg
)
141 //::printf("writeToLogFile(\'%s\', \'%s\') fileName = \'%s\'\n", qPrintable(src.name), qPrintable(msg), qPrintable(src.fileName));
142 if (src
.fileName
.isEmpty())
144 QFile
f(src
.fileName
);
145 QFile::OpenMode mode
;
147 mode
= QFile::Append
| QFile::Text
| QFile::Unbuffered
;
149 mode
= QFile::Append
| QFile::Text
;
155 // Write to the log file and then close the file
156 f
.write(msg
.toLocal8Bit());
159 // Check the file size
160 if (src
.maxSize
> 0 && f
.size() > src
.maxSize
) {
162 QDir
dir(QFileInfo(src
.fileName
).dir());
163 QString baseName
= QFileInfo(src
.fileName
).fileName();
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));
169 // Shift backup files (.0 -> .1, .1 -> .2 etc)
170 renameBackupFile(dir
, baseName
, 0);
172 // Rename the current log file to the backup #0 file
173 dir
.rename(baseName
, baseName
+ ".0");
180 //-------------------------------------------------------------------
182 void Logger::destroyInstance()
184 if (singleton
!= nullptr)
194 , mFatalMsgHandler(defFatalMsgHandler
)
195 , mConsoleSeverity(iLogger::Fatal
)
197 setObjectName(QString("%1-iLogger").arg(VER_MODULE_NAME_STR
));
199 qRegisterMetaType
<LoggerSource
>("LoggerSource");
201 // Create the default source
202 mDefaultSource
= new LoggerSource
;
203 mDefaultSource
->name
= "common";
205 write(Info
, QString("%1 created").arg(objectName()), QString(), printf("%s:%s:%d", __FILE__
, __FUNCTION__
, __LINE__
));
210 // Disconnect any potential receivers from this object
211 disconnect(this, SIGNAL(loggerEvent(Common::iLogger::Severity
,QString
,QString
,QString
)), nullptr, nullptr);
213 // Destroy the worker thread
223 write(Info
, QString("%1 destroyed").arg(objectName()), QString(), printf("%s:%s:%d", __FILE__
, __FUNCTION__
, __LINE__
));
228 // Register our interface
229 iRegistry::instance()->registerInterface("iLogger", this);
231 // Clear existing sources in case the application was restarted
234 // Set the default source name to the name of the application
235 setDefaultSource(iApp::instance()->name());
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());
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());
243 setMaxSize(v
.toUInt());
244 v
= iConfig::instance()->getValue(QString("%1/general/log_cnt").arg(iApp::instance()->name()), maxCount());
246 setMaxCount(v
.toUInt());
248 // Destroy the previous worker thread
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
);
263 write(Info
, QString("%1 initialized").arg(objectName()), QString(), printf("%s:%s:%d", __FILE__
, __FUNCTION__
, __LINE__
));
268 QString
Logger::defaultSource() const
270 return mDefaultSource
->name
;
273 void Logger::setDefaultSource(QString
const & source
)
275 LoggerSource
* src
= getSource();
276 if (src
&& src
->name
!= source
)
277 getSource(QString())->init(source
);
280 iLogger::Severity
Logger::severity(QString
const & source
)
282 return getSource(source
)->severity
;
285 void Logger::setSeverity(iLogger::Severity severity
, QString
const & source
)
287 getSource(source
)->severity
= severity
;
290 uint
Logger::maxSize(QString
const & source
)
292 return getSource(source
)->maxSize
;
295 void Logger::setMaxSize(uint maxSize
, QString
const & source
)
297 getSource(source
)->maxSize
= maxSize
* 1024;
300 uint
Logger::maxCount(QString
const & source
)
302 return getSource(source
)->maxCount
;
305 void Logger::setMaxCount(uint maxCount
, QString
const & source
)
307 getSource(source
)->maxCount
= maxCount
;
310 void Logger::setConsoleSeverity(iLogger::Severity severity
)
312 mConsoleSeverity
= severity
;
315 void Logger::write(Severity severity
, QString
const & msg
, QString
const & source
, QString
const & where
)
317 static char const * const severityText
[] =
327 // Make sure that we don't output messages with the None severity
328 if (severity
== iLogger::None
)
331 // Write to the log file
333 LoggerSource
* src
= getSource(source
);
334 if (severity
<= src
->severity
&& src
->severity
!= iLogger::None
) {
336 QTextStream
io(&buf
);
339 io
<< QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
342 io
<< " " << severityText
[severity
];
347 // Location in the source file
348 if (!where
.isEmpty())
349 io
<< " (occurred in " << where
<< ")";
354 emit
writeToLogFile(*src
, buf
);
358 // Output to the console
359 if (source
.isEmpty() && severity
<= mConsoleSeverity
&& mConsoleSeverity
!= iLogger::None
) {
360 FILE * f
= (severity
< iLogger::Info
) ? stderr
: stdout
;
363 #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
366 fprintf(f
, "\033[32m"); // Green
368 case iLogger::Warning
:
369 fprintf(f
, "\033[1m"); // Bold
372 fprintf(f
, "\033[31m"); // Red
375 fprintf(f
, "\033[31m\033[1m"); // Bold Red
378 fprintf(f
, "\033[34m"); // Blue
381 #elif defined(Q_OS_WIN32)
384 setColor(FOREGROUND_GREEN
);
386 case iLogger::Warning
:
387 setColor(FOREGROUND_INTENSITY
| FOREGROUND_GREEN
| FOREGROUND_BLUE
| FOREGROUND_RED
);
390 setColor(FOREGROUND_RED
| FOREGROUND_INTENSITY
);
393 setColor(FOREGROUND_RED
| FOREGROUND_GREEN
| FOREGROUND_INTENSITY
| BACKGROUND_RED
);
396 setColor(FOREGROUND_BLUE
);
401 // Output the message
402 fprintf(f
, "%s %s\n", severityText
[severity
], qPrintable(msg
));
405 if (!where
.isEmpty())
406 fprintf(f
, "\t(occurred in %s)\n\n", qPrintable(where
));
409 #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
411 #elif defined(Q_OS_WIN32)
420 emit
loggerEvent(severity
, msg
, source
, where
);
422 // Handle fatal error messages
423 if (severity
== iLogger::Fatal
&& mFatalMsgHandler
)
424 mFatalMsgHandler(msg
, source
, where
);
427 QString
Logger::printf(char const * const fmt
, ...) const
432 char * str
= nullptr;
439 vsnprintf(str
, sizeof(str
), fmt
, ap
);
441 _vsnprintf_s(str
, sizeof(str
), _TRUNCATE
, fmt
, ap
);
446 if (::vasprintf(&str
, fmt
, ap
)) {} // IF is needed to avoid the compiler warning
459 QString
Logger::printable(QByteArray
const & msg
) const
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"
470 for (int i
= 0; i
< sz
; ++i
) {
471 uchar ch
= uchar(msg
.at(i
));
473 rval
.append(QString("[%1]").arg(ctrlChars
[ch
]));
475 rval
.append(msg
.at(i
));
477 rval
.append("[DEL]");
479 rval
.append(QString("[\\x%1]").arg(ch
, 2, 16, QChar('0')));
485 FatalMsgHandler
Logger::installFatalMsgHandler(FatalMsgHandler newHandler
)
487 FatalMsgHandler oldHandler
= mFatalMsgHandler
;
488 mFatalMsgHandler
= newHandler
;
492 LoggerSource
* Logger::getSource(QString
const & source
)
494 if (source
.isEmpty() || source
== mDefaultSource
->name
)
495 return mDefaultSource
.data();
497 QHash
<QString
, QExplicitlySharedDataPointer
<LoggerSource
> >::const_iterator it
= mSources
.constFind(source
);
498 if (it
!= mSources
.constEnd())
501 // Create the new source
502 QExplicitlySharedDataPointer
<LoggerSource
> src(new LoggerSource
);
503 mSources
.insert(source
, src
);
505 // Initialize the new source
513 void Logger::setColor(short int c
)
515 HANDLE handle
= GetStdHandle(STD_OUTPUT_HANDLE
);
516 SetConsoleTextAttribute(handle
, c
);