From c876d3bbefe0dc00c27ca0c12d29da5874946962 Mon Sep 17 00:00:00 2001 From: Dominik Riebeling Date: Wed, 15 Dec 2021 21:04:28 +0100 Subject: rbutil: Merge rbutil with utils folder. rbutil uses several components from the utils folder, and can be considered part of utils too. Having it in a separate folder is an arbitrary split that doesn't help anymore these days, so merge them. This also allows other utils to easily use libtools.make without the need to navigate to a different folder. Change-Id: I3fc2f4de19e3e776553efb5dea5f779dfec0dc21 --- utils/rbutilqt/logger/src/AbstractAppender.cpp | 147 +++ .../rbutilqt/logger/src/AbstractStringAppender.cpp | 460 ++++++++ utils/rbutilqt/logger/src/ConsoleAppender.cpp | 64 ++ utils/rbutilqt/logger/src/FileAppender.cpp | 116 ++ utils/rbutilqt/logger/src/Logger.cpp | 1108 ++++++++++++++++++++ utils/rbutilqt/logger/src/OutputDebugAppender.cpp | 43 + 6 files changed, 1938 insertions(+) create mode 100644 utils/rbutilqt/logger/src/AbstractAppender.cpp create mode 100644 utils/rbutilqt/logger/src/AbstractStringAppender.cpp create mode 100644 utils/rbutilqt/logger/src/ConsoleAppender.cpp create mode 100644 utils/rbutilqt/logger/src/FileAppender.cpp create mode 100644 utils/rbutilqt/logger/src/Logger.cpp create mode 100644 utils/rbutilqt/logger/src/OutputDebugAppender.cpp (limited to 'utils/rbutilqt/logger/src') diff --git a/utils/rbutilqt/logger/src/AbstractAppender.cpp b/utils/rbutilqt/logger/src/AbstractAppender.cpp new file mode 100644 index 0000000000..778bbddd11 --- /dev/null +++ b/utils/rbutilqt/logger/src/AbstractAppender.cpp @@ -0,0 +1,147 @@ +/* + Copyright (c) 2010 Boris Moiseev (cyberbobs at gmail dot com) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation and appearing in the file + LICENSE.LGPL included in the packaging of this file. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. +*/ +// Local +#include "AbstractAppender.h" + +// Qt +#include + + +/** + * \class AbstractAppender + * + * \brief The AbstractAppender class provides an abstract base class for writing a log entries. + * + * The AbstractAppender class is the base interface class for all log appenders that could be used with Logger. + * + * AbstractAppender provides a common implementation for the thread safe, mutex-protected logging of application + * messages, such as ConsoleAppender, FileAppender or something else. AbstractAppender is abstract and can not be + * instantiated, but you can use any of its subclasses or create a custom log appender at your choice. + * + * Appenders are the logical devices that is aimed to be attached to Logger object by calling + * Logger::registerAppender(). On each log record call from the application Logger object sequentially calls write() + * function on all the appenders registered in it. + * + * You can subclass AbstractAppender to implement a logging target of any kind you like. It may be the external logging + * subsystem (for example, syslog in *nix), XML file, SQL database entries, D-Bus messages or anything else you can + * imagine. + * + * For the simple non-structured plain text logging (for example, to a plain text file or to the console output) you may + * like to subclass the AbstractStringAppender instead of AbstractAppender, which will give you a more convinient way to + * control the format of the log output. + * + * \sa AbstractStringAppender + * \sa Logger::registerAppender() + */ + + +//! Constructs a AbstractAppender object. +AbstractAppender::AbstractAppender() + : m_detailsLevel(Logger::Debug) +{} + + +//! Destructs the AbstractAppender object. +AbstractAppender::~AbstractAppender() +{} + + +//! Returns the current details level of appender. +/** + * Log records with a log level lower than a current detailsLevel() will be silently ignored by appender and would not + * be sent to its append() function. + * + * It provides additional logging flexibility, allowing you to set the different severity levels for different types + * of logs. + * + * \note This function is thread safe. + * + * \sa setDetailsLevel() + * \sa Logger::LogLevel + */ +Logger::LogLevel AbstractAppender::detailsLevel() const +{ + QMutexLocker locker(&m_detailsLevelMutex); + return m_detailsLevel; +} + + +//! Sets the current details level of appender. +/** + * Default details level is Logger::Debug + * + * \note This function is thread safe. + * + * \sa detailsLevel() + * \sa Logger::LogLevel + */ +void AbstractAppender::setDetailsLevel(Logger::LogLevel level) +{ + QMutexLocker locker(&m_detailsLevelMutex); + m_detailsLevel = level; +} + + + +//! Sets the current details level of appender +/** + * This function is provided for convenience, it behaves like an above function. + * + * \sa detailsLevel() + * \sa Logger::LogLevel + */ +void AbstractAppender::setDetailsLevel(const QString& level) +{ + setDetailsLevel(Logger::levelFromString(level)); +} + + +//! Tries to write the log record to this logger +/** + * This is the function called by Logger object to write a log message to the appender. + * + * \note This function is thread safe. + * + * \sa Logger::write() + * \sa detailsLevel() + */ +void AbstractAppender::write(const QDateTime& timeStamp, Logger::LogLevel logLevel, const char* file, int line, + const char* function, const QString& category, const QString& message) +{ + if (logLevel >= detailsLevel()) + { + QMutexLocker locker(&m_writeMutex); + append(timeStamp, logLevel, file, line, function, category, message); + } +} + + +/** + * \fn virtual void AbstractAppender::append(const QDateTime& timeStamp, Logger::LogLevel logLevel, const char* file, + * int line, const char* function, const QString& message) + * + * \brief Writes the log record to the logger instance + * + * This function is called every time when user tries to write a message to this AbstractAppender instance using + * the write() function. Write function works as proxy and transfers only the messages with log level more or equal + * to the current logLevel(). + * + * Overload this function when you are implementing a custom appender. + * + * \note This function is not needed to be thread safe because it is never called directly by Logger object. The + * write() function works as a proxy and protects this function from concurrent access. + * + * \sa Logger::write() + */ + diff --git a/utils/rbutilqt/logger/src/AbstractStringAppender.cpp b/utils/rbutilqt/logger/src/AbstractStringAppender.cpp new file mode 100644 index 0000000000..ea5883f744 --- /dev/null +++ b/utils/rbutilqt/logger/src/AbstractStringAppender.cpp @@ -0,0 +1,460 @@ +/* + Copyright (c) 2010 Boris Moiseev (cyberbobs at gmail dot com) Nikolay Matyunin (matyunin.n at gmail dot com) + + Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation and appearing in the file + LICENSE.LGPL included in the packaging of this file. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. +*/ +// Local +#include "AbstractStringAppender.h" + +// Qt +#include +#include +#include +#include +#include +#include + + +/** + * \class AbstractStringAppender + * + * \brief The AbstractStringAppender class provides a convinient base for appenders working with plain text formatted + * logs. + * + * AbstractSringAppender is the simple extension of the AbstractAppender class providing the convinient way to create + * custom log appenders working with a plain text formatted log targets. + * + * It have the formattedString() protected function that formats the logging arguments according to a format set with + * setFormat(). + * + * This class can not be directly instantiated because it contains pure virtual function inherited from AbstractAppender + * class. + * + * For more detailed description of customizing the log output format see the documentation on the setFormat() function. + */ + + +const char formattingMarker = '%'; + + +//! Constructs a new string appender object +AbstractStringAppender::AbstractStringAppender() + : m_format(QLatin1String("%{time}{yyyy-MM-ddTHH:mm:ss.zzz} [%{type:-7}] <%{function}> %{message}\n")) +{} + + +//! Returns the current log format string. +/** + * The default format is set to "%{time}{yyyy-MM-ddTHH:mm:ss.zzz} [%{type:-7}] <%{function}> %{message}\n". You can set a different log record + * format using the setFormat() function. + * + * \sa setFormat(const QString&) + */ +QString AbstractStringAppender::format() const +{ + QReadLocker locker(&m_formatLock); + return m_format; +} + + +//! Sets the logging format for writing strings to the log target with this appender. +/** + * The string format seems to be very common to those developers who have used a standart sprintf function. + * + * Log output format is a simple QString with the special markers (starting with % sign) which will be replaced with + * it's internal meaning when writing a log record. + * + * Controlling marker begins with the percent sign (%) which is followed by the command inside {} brackets + * (the command describes, what will be put to log record instead of marker). + * Optional field width argument may be specified right after the command (through the colon symbol before the closing bracket) + * Some commands requires an additional formatting argument (in the second {} brackets). + * + * Field width argument works almost identically to the \c QString::arg() \c fieldWidth argument (and uses it + * internally). For example, \c "%{type:-7}" will be replaced with the left padded debug level of the message + * (\c "Debug ") or something. For the more detailed description of it you may consider to look to the Qt + * Reference Documentation. + * + * Supported marker commands are: + * \arg \c %{time} - timestamp. You may specify your custom timestamp format using the second {} brackets after the marker, + * timestamp format here will be similiar to those used in QDateTime::toString() function. For example, + * "%{time}{dd-MM-yyyy, HH:mm}" may be replaced with "17-12-2010, 20:17" depending on current date and time. + * The default format used here is "HH:mm:ss.zzz". + * \arg \c %{type} - Log level. Possible log levels are shown in the Logger::LogLevel enumerator. + * \arg \c %{Type} - Uppercased log level. + * \arg \c %{typeOne} - One letter log level. + * \arg \c %{TypeOne} - One uppercase letter log level. + * \arg \c %{File} - Full source file name (with path) of the file that requested log recording. Uses the \c __FILE__ + * preprocessor macro. + * \arg \c %{file} - Short file name (with stripped path). + * \arg \c %{line} - Line number in the source file. Uses the \c __LINE__ preprocessor macro. + * \arg \c %{Function} - Name of function that called on of the LOG_* macros. Uses the \c Q_FUNC_INFO macro provided with + * Qt. + * \arg \c %{function} - Similiar to the %{Function}, but the function name is stripped using stripFunctionName + * \arg \c %{message} - The log message sent by the caller. + * \arg \c %{category} - The log category. + * \arg \c %{appname} - Application name (returned by QCoreApplication::applicationName() function). + * \arg \c %{pid} - Application pid (returned by QCoreApplication::applicationPid() function). + * \arg \c %{threadid} - ID of current thread. + * \arg \c %% - Convinient marker that is replaced with the single \c % mark. + * + * \note Format doesn't add \c '\\n' to the end of the format line. Please consider adding it manually. + * + * \sa format() + * \sa stripFunctionName() + * \sa Logger::LogLevel + */ +void AbstractStringAppender::setFormat(const QString& format) +{ + QWriteLocker locker(&m_formatLock); + m_format = format; +} + + +//! Strips the long function signature (as added by Q_FUNC_INFO macro) +/** + * The string processing drops the returning type, arguments and template parameters of function. It is definitely + * useful for enchancing the log output readability. + * \return stripped function name + */ +QString AbstractStringAppender::stripFunctionName(const char* name) +{ + return QString::fromLatin1(qCleanupFuncinfo(name)); +} + + +// The function was backported from Qt5 sources (qlogging.h) +QByteArray AbstractStringAppender::qCleanupFuncinfo(const char* name) +{ + QByteArray info(name); + + // Strip the function info down to the base function name + // note that this throws away the template definitions, + // the parameter types (overloads) and any const/volatile qualifiers. + if (info.isEmpty()) + return info; + + int pos; + + // skip trailing [with XXX] for templates (gcc) + pos = info.size() - 1; + if (info.endsWith(']')) { + while (--pos) { + if (info.at(pos) == '[') + info.truncate(pos); + } + } + + bool hasLambda = false; + QRegularExpression lambdaRegex("::"); + QRegularExpressionMatch match = lambdaRegex.match(QString::fromLatin1(info)); + int lambdaIndex = match.capturedStart(); + if (lambdaIndex != -1) + { + hasLambda = true; + info.remove(lambdaIndex, match.capturedLength()); + } + + // operator names with '(', ')', '<', '>' in it + static const char operator_call[] = "operator()"; + static const char operator_lessThan[] = "operator<"; + static const char operator_greaterThan[] = "operator>"; + static const char operator_lessThanEqual[] = "operator<="; + static const char operator_greaterThanEqual[] = "operator>="; + + // canonize operator names + info.replace("operator ", "operator"); + + // remove argument list + forever { + int parencount = 0; + pos = info.lastIndexOf(')'); + if (pos == -1) { + // Don't know how to parse this function name + return info; + } + + // find the beginning of the argument list + --pos; + ++parencount; + while (pos && parencount) { + if (info.at(pos) == ')') + ++parencount; + else if (info.at(pos) == '(') + --parencount; + --pos; + } + if (parencount != 0) + return info; + + info.truncate(++pos); + + if (info.at(pos - 1) == ')') { + if (info.indexOf(operator_call) == pos - (int)strlen(operator_call)) + break; + + // this function returns a pointer to a function + // and we matched the arguments of the return type's parameter list + // try again + info.remove(0, info.indexOf('(')); + info.chop(1); + continue; + } else { + break; + } + } + + if (hasLambda) + info.append("::lambda"); + + // find the beginning of the function name + int parencount = 0; + int templatecount = 0; + --pos; + + // make sure special characters in operator names are kept + if (pos > -1) { + switch (info.at(pos)) { + case ')': + if (info.indexOf(operator_call) == pos - (int)strlen(operator_call) + 1) + pos -= 2; + break; + case '<': + if (info.indexOf(operator_lessThan) == pos - (int)strlen(operator_lessThan) + 1) + --pos; + break; + case '>': + if (info.indexOf(operator_greaterThan) == pos - (int)strlen(operator_greaterThan) + 1) + --pos; + break; + case '=': { + int operatorLength = (int)strlen(operator_lessThanEqual); + if (info.indexOf(operator_lessThanEqual) == pos - operatorLength + 1) + pos -= 2; + else if (info.indexOf(operator_greaterThanEqual) == pos - operatorLength + 1) + pos -= 2; + break; + } + default: + break; + } + } + + while (pos > -1) { + if (parencount < 0 || templatecount < 0) + return info; + + char c = info.at(pos); + if (c == ')') + ++parencount; + else if (c == '(') + --parencount; + else if (c == '>') + ++templatecount; + else if (c == '<') + --templatecount; + else if (c == ' ' && templatecount == 0 && parencount == 0) + break; + + --pos; + } + info = info.mid(pos + 1); + + // remove trailing '*', '&' that are part of the return argument + while ((info.at(0) == '*') + || (info.at(0) == '&')) + info = info.mid(1); + + // we have the full function name now. + // clean up the templates + while ((pos = info.lastIndexOf('>')) != -1) { + if (!info.contains('<')) + break; + + // find the matching close + int end = pos; + templatecount = 1; + --pos; + while (pos && templatecount) { + char c = info.at(pos); + if (c == '>') + ++templatecount; + else if (c == '<') + --templatecount; + --pos; + } + ++pos; + info.remove(pos, end - pos + 1); + } + + return info; +} + + +//! Returns the string to record to the logging target, formatted according to the format(). +/** + * \sa format() + * \sa setFormat(const QString&) + */ +QString AbstractStringAppender::formattedString(const QDateTime& timeStamp, Logger::LogLevel logLevel, const char* file, + int line, const char* function, const QString& category, const QString& message) const +{ + QString f = format(); + const int size = f.size(); + + QString result; + + int i = 0; + while (i < f.size()) + { + QChar c = f.at(i); + + // We will silently ignore the broken % marker at the end of string + if (c != QLatin1Char(formattingMarker) || (i + 2) >= size) + { + result.append(c); + } + else + { + i += 2; + QChar currentChar = f.at(i); + QString command; + int fieldWidth = 0; + + if (currentChar.isLetter()) + { + command.append(currentChar); + int j = 1; + while ((i + j) < size && f.at(i + j).isLetter()) + { + command.append(f.at(i+j)); + j++; + } + + i+=j; + currentChar = f.at(i); + + // Check for the padding instruction + if (currentChar == QLatin1Char(':')) + { + currentChar = f.at(++i); + if (currentChar.isDigit() || currentChar.category() == QChar::Punctuation_Dash) + { + int j = 1; + while ((i + j) < size && f.at(i + j).isDigit()) + j++; + fieldWidth = f.mid(i, j).toInt(); + + i += j; + } + } + } + + // Log record chunk to insert instead of formatting instruction + QString chunk; + + // Time stamp + if (command == QLatin1String("time")) + { + if (f.at(i + 1) == QLatin1Char('{')) + { + int j = 1; + while ((i + 2 + j) < size && f.at(i + 2 + j) != QLatin1Char('}')) + j++; + + if ((i + 2 + j) < size) + { + chunk = timeStamp.toString(f.mid(i + 2, j)); + + i += j; + i += 2; + } + } + + if (chunk.isNull()) + chunk = timeStamp.toString(QLatin1String("HH:mm:ss.zzz")); + } + + // Log level + else if (command == QLatin1String("type")) + chunk = Logger::levelToString(logLevel); + + // Uppercased log level + else if (command == QLatin1String("Type")) + chunk = Logger::levelToString(logLevel).toUpper(); + + // One letter log level + else if (command == QLatin1String("typeOne")) + chunk = Logger::levelToString(logLevel).left(1).toLower(); + + // One uppercase letter log level + else if (command == QLatin1String("TypeOne")) + chunk = Logger::levelToString(logLevel).left(1).toUpper(); + + // Filename + else if (command == QLatin1String("File")) + chunk = QLatin1String(file); + + // Filename without a path + else if (command == QLatin1String("file")) + chunk = QString(QLatin1String(file)).section(QRegularExpression("[/\\\\]"), -1); + + // Source line number + else if (command == QLatin1String("line")) + chunk = QString::number(line); + + // Function name, as returned by Q_FUNC_INFO + else if (command == QLatin1String("Function")) + chunk = QString::fromLatin1(function); + + // Stripped function name + else if (command == QLatin1String("function")) + chunk = stripFunctionName(function); + + // Log message + else if (command == QLatin1String("message")) + chunk = message; + + else if (command == QLatin1String("category")) + chunk = category; + + // Application pid + else if (command == QLatin1String("pid")) + chunk = QString::number(QCoreApplication::applicationPid()); + + // Appplication name + else if (command == QLatin1String("appname")) + chunk = QCoreApplication::applicationName(); + + // Thread ID (duplicates Qt5 threadid debbuging way) + else if (command == QLatin1String("threadid")) + chunk = QLatin1String("0x") + QString::number(qlonglong(QThread::currentThread()->currentThread()), 16); + + // We simply replace the double formatting marker (%) with one + else if (command == QString(formattingMarker)) + chunk = QLatin1Char(formattingMarker); + + // Do not process any unknown commands + else + { + chunk = QString(formattingMarker); + chunk.append(command); + } + + result.append(QString(QLatin1String("%1")).arg(chunk, fieldWidth)); + } + + ++i; + } + + return result; +} diff --git a/utils/rbutilqt/logger/src/ConsoleAppender.cpp b/utils/rbutilqt/logger/src/ConsoleAppender.cpp new file mode 100644 index 0000000000..932ffab787 --- /dev/null +++ b/utils/rbutilqt/logger/src/ConsoleAppender.cpp @@ -0,0 +1,64 @@ +/* + Copyright (c) 2010 Boris Moiseev (cyberbobs at gmail dot com) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation and appearing in the file + LICENSE.LGPL included in the packaging of this file. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. +*/ +// Local +#include "ConsoleAppender.h" + +// STL +#include + + +/** + * \class ConsoleAppender + * + * \brief ConsoleAppender is the simple appender that writes the log records to the std::cerr output stream. + * + * ConsoleAppender uses "[%{type:-7}] <%{function}> %{message}\n" as a default output format. It is similar to the + * AbstractStringAppender but doesn't show a timestamp. + * + * You can modify ConsoleAppender output format without modifying your code by using \c QT_MESSAGE_PATTERN environment + * variable. If you need your application to ignore this environment variable you can call + * ConsoleAppender::ignoreEnvironmentPattern(true) + */ + + +ConsoleAppender::ConsoleAppender() + : AbstractStringAppender() + , m_ignoreEnvPattern(false) +{ + setFormat("[%{type:-7}] <%{function}> %{message}\n"); +} + + +QString ConsoleAppender::format() const +{ + const QString envPattern = QString::fromLocal8Bit(qgetenv("QT_MESSAGE_PATTERN")); + return (m_ignoreEnvPattern || envPattern.isEmpty()) ? AbstractStringAppender::format() : (envPattern + "\n"); +} + + +void ConsoleAppender::ignoreEnvironmentPattern(bool ignore) +{ + m_ignoreEnvPattern = ignore; +} + + +//! Writes the log record to the std::cerr stream. +/** + * \sa AbstractStringAppender::format() + */ +void ConsoleAppender::append(const QDateTime& timeStamp, Logger::LogLevel logLevel, const char* file, int line, + const char* function, const QString& category, const QString& message) +{ + std::cerr << qPrintable(formattedString(timeStamp, logLevel, file, line, function, category, message)); +} diff --git a/utils/rbutilqt/logger/src/FileAppender.cpp b/utils/rbutilqt/logger/src/FileAppender.cpp new file mode 100644 index 0000000000..b9018b0324 --- /dev/null +++ b/utils/rbutilqt/logger/src/FileAppender.cpp @@ -0,0 +1,116 @@ +/* + Copyright (c) 2010 Boris Moiseev (cyberbobs at gmail dot com) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation and appearing in the file + LICENSE.LGPL included in the packaging of this file. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. +*/ +// Local +#include "FileAppender.h" + +// STL +#include + +/** + * \class FileAppender + * + * \brief Simple appender that writes the log records to the plain text file. + */ + + +//! Constructs the new file appender assigned to file with the given name. +FileAppender::FileAppender(const QString& fileName) +{ + setFileName(fileName); +} + + +FileAppender::~FileAppender() +{ + closeFile(); +} + + +//! Returns the name set by setFileName() or to the FileAppender constructor. +/** + * \sa setFileName() + */ +QString FileAppender::fileName() const +{ + QMutexLocker locker(&m_logFileMutex); + return m_logFile.fileName(); +} + + +//! Sets the name of the file. The name can have no path, a relative path, or an absolute path. +/** + * \sa fileName() + */ +void FileAppender::setFileName(const QString& s) +{ + if (s.isEmpty()) + std::cerr << " File name is empty. The appender will do nothing" << std::endl; + + QMutexLocker locker(&m_logFileMutex); + if (m_logFile.isOpen()) + m_logFile.close(); + + m_logFile.setFileName(s); +} + + +bool FileAppender::reopenFile() +{ + closeFile(); + return openFile(); +} + + +bool FileAppender::openFile() +{ + if (m_logFile.fileName().isEmpty()) + return false; + + bool isOpen = m_logFile.isOpen(); + if (!isOpen) + { + isOpen = m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text); + if (isOpen) + m_logStream.setDevice(&m_logFile); + else + std::cerr << " Cannot open the log file " << qPrintable(m_logFile.fileName()) << std::endl; + } + return isOpen; +} + + +//! Write the log record to the file. +/** + * \sa fileName() + * \sa AbstractStringAppender::format() + */ +void FileAppender::append(const QDateTime& timeStamp, Logger::LogLevel logLevel, const char* file, int line, + const char* function, const QString& category, const QString& message) +{ + QMutexLocker locker(&m_logFileMutex); + + if (openFile()) + { + m_logStream << formattedString(timeStamp, logLevel, file, line, function, category, message); + m_logStream.flush(); + m_logFile.flush(); + } +} + + +void FileAppender::closeFile() +{ + QMutexLocker locker(&m_logFileMutex); + m_logFile.close(); +} diff --git a/utils/rbutilqt/logger/src/Logger.cpp b/utils/rbutilqt/logger/src/Logger.cpp new file mode 100644 index 0000000000..689bc42e80 --- /dev/null +++ b/utils/rbutilqt/logger/src/Logger.cpp @@ -0,0 +1,1108 @@ +/* + Copyright (c) 2012 Boris Moiseev (cyberbobs at gmail dot com) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation and appearing in the file + LICENSE.LGPL included in the packaging of this file. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. +*/ +// Local +#include "Logger.h" +#include "AbstractAppender.h" +#include "AbstractStringAppender.h" + +// Qt +#include +#include +#include +#include +#include +#include + +#if defined(Q_OS_ANDROID) +# include +# include +#endif + +// STL +#include + + +/** + * \file Logger.h + * \brief A file containing the description of Logger class and and additional useful macros for logging + */ + + +/** + * \mainpage + * + * Logger is a simple way to write the history of your application lifecycle to any target logging device (which is + * called Appender and may write to any target you will implement with it: console, text file, XML or something - you + * choose) and to map logging message to a class, function, source file and line of code which it is called from. + * + * Some simple appenders (which may be considered an examples) are provided with the Logger itself: see ConsoleAppender + * and FileAppender documentation. + * + * It supports using it in a multithreaded applications, so all of its functions are thread safe. + * + * Simple usage example: + * \code + * #include + * + * #include + * #include + * + * int main(int argc, char* argv[]) + * { + * QCoreApplication app(argc, argv); + * ... + * ConsoleAppender* consoleAppender = new ConsoleAppender; + * consoleAppender->setFormat("[%{type:-7}] <%{Function}> %{message}\n"); + * cuteLogger->registerAppender(consoleAppender); + * ... + * LOG_INFO("Starting the application"); + * int result = app.exec(); + * ... + * if (result) + * LOG_WARNING() << "Something went wrong." << "Result code is" << result; + * + * return result; + * } + * \endcode + * + * Logger internally uses the lazy-initialized singleton object and needs no definite initialization, but you may + * consider registering a log appender before calling any log recording functions or macros. + * + * The library design of Logger allows you to simply mass-replace all occurrences of qDebug and similar calls with + * similar Logger macros (e.g. LOG_DEBUG()) + * + * \note Logger uses a singleton global instance which lives through all the application life cycle and self-destroys + * destruction of the QCoreApplication (or QApplication) instance. It needs a QCoreApplication instance to be + * created before any of the Logger's functions are called. + * + * \sa cuteLogger + * \sa LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL + * \sa LOG_CTRACE, LOG_CDEBUG, LOG_CINFO, LOG_CWARNING, LOG_CERROR, LOG_CFATAL + * \sa LOG_ASSERT + * \sa LOG_TRACE_TIME, LOG_DEBUG_TIME, LOG_INFO_TIME + * \sa AbstractAppender + */ + + +/** + * \def cuteLogger + * + * \brief Macro returning the current instance of Logger object + * + * If you haven't created a local Logger object it returns the same value as the Logger::globalInstance() functions. + * This macro is a recommended way to get an access to the Logger instance used in current class. + * + * Example: + * \code + * ConsoleAppender* consoleAppender = new ConsoleAppender; + * cuteLogger->registerAppender(consoleAppender); + * \endcode + * + * \sa Logger::globalInstance() + */ + + +/** + * \def LOG_TRACE + * + * \brief Writes the trace log record + * + * This macro is the convinient way to call Logger::write(). It uses the common preprocessor macros \c __FILE__, + * \c __LINE__ and the standart Qt \c Q_FUNC_INFO macros to automatically determine the needed parameters to call + * Logger::write(). + * + * \note This and other (LOG_INFO() etc...) macros uses the variadic macro arguments to give convinient usage form for + * the different versions of Logger::write() (using the QString or const char* argument or returning the QDebug class + * instance). Not all compilers will support this. Please, consider reviewing your compiler documentation to ensure + * it support __VA_ARGS__ macro. + * + * \sa Logger::LogLevel + * \sa Logger::write() + */ + + +/** + * \def LOG_DEBUG + * + * \brief Writes the debug log record + * + * This macro records the debug log record using the Logger::write() function. It works similar to the LOG_TRACE() + * macro. + * + * \sa LOG_TRACE() + * \sa Logger::LogLevel + * \sa Logger::write() + */ + + +/** + * \def LOG_INFO + * + * \brief Writes the info log record + * + * This macro records the info log record using the Logger::write() function. It works similar to the LOG_TRACE() + * macro. + * + * \sa LOG_TRACE() + * \sa Logger::LogLevel + * \sa Logger::write() + */ + + +/** + * \def LOG_WARNING + * + * \brief Write the warning log record + * + * This macro records the warning log record using the Logger::write() function. It works similar to the LOG_TRACE() + * macro. + * + * \sa LOG_TRACE() + * \sa Logger::LogLevel + * \sa Logger::write() + */ + + +/** + * \def LOG_ERROR + * + * \brief Write the error log record + * This macro records the error log record using the Logger::write() function. It works similar to the LOG_TRACE() + * macro. + * + * \sa LOG_TRACE() + * \sa Logger::LogLevel + * \sa Logger::write() + */ + + +/** + * \def LOG_FATAL + * + * \brief Write the fatal log record + * + * This macro records the fatal log record using the Logger::write() function. It works similar to the LOG_TRACE() + * macro. + * + * \note Recording of the log record using the Logger::Fatal log level will lead to calling the STL abort() + * function, which will interrupt the running of your software and begin the writing of the core dump. + * + * \sa LOG_TRACE() + * \sa Logger::LogLevel + * \sa Logger::write() + */ + + +/** + * \def LOG_CTRACE(category) + * + * \brief Writes the trace log record to the specific category + * + * This macro is the similar to the LOG_TRACE() macro, but has a category parameter + * to write only to the category appenders (registered using Logger::registerCategoryAppender() method). + * + * \param category category name string + * + * \sa LOG_TRACE() + * \sa Logger::LogLevel + * \sa Logger::registerCategoryAppender() + * \sa Logger::write() + * \sa LOG_CATEGORY(), LOG_GLOBAL_CATEGORY() + */ + + +/** + * \def LOG_CDEBUG + * + * \brief Writes the debug log record to the specific category + * + * This macro records the debug log record using the Logger::write() function. It works similar to the LOG_CTRACE() + * macro. + * + * \sa LOG_CTRACE() + */ + + +/** + * \def LOG_CINFO + * + * \brief Writes the info log record to the specific category + * + * This macro records the info log record using the Logger::write() function. It works similar to the LOG_CTRACE() + * macro. + * + * \sa LOG_CTRACE() + */ + + +/** + * \def LOG_CWARNING + * + * \brief Writes the warning log record to the specific category + * + * This macro records the warning log record using the Logger::write() function. It works similar to the LOG_CTRACE() + * macro. + * + * \sa LOG_CTRACE() + */ + + +/** + * \def LOG_CERROR + * + * \brief Writes the error log record to the specific category + * + * This macro records the error log record using the Logger::write() function. It works similar to the LOG_CTRACE() + * macro. + * + * \sa LOG_CTRACE() + */ + + +/** + * \def LOG_CFATAL + * + * \brief Write the fatal log record to the specific category + * + * This macro records the fatal log record using the Logger::write() function. It works similar to the LOG_CTRACE() + * macro. + * + * \note Recording of the log record using the Logger::Fatal log level will lead to calling the STL abort() + * function, which will interrupt the running of your software and begin the writing of the core dump. + * + * \sa LOG_CTRACE() + */ + + +/** + * \def LOG_CATEGORY(category) + * + * \brief Create logger instance inside your custom class to log all messages to the specified category + * + * This macro is used to pass all log messages inside your custom class to the specific category. + * You must include this macro inside your class declaration (similarly to the Q_OBJECT macro). + * Internally, this macro redefines cuteLoggerInstance() function, creates the local Logger object inside your class and + * sets the default category to the specified parameter. + * + * Thus, any call to cuteLoggerInstance() (for example, inside LOG_TRACE() macro) will return the local Logger object, + * so any logging message will be directed to the default category. + * + * \note This macro does not register any appender to the newly created logger instance. You should register + * logger appenders manually, inside your class. + * + * Usage example: + * \code + * class CustomClass : public QObject + * { + * Q_OBJECT + * LOG_CATEGORY("custom_category") + * ... + * }; + * + * CustomClass::CustomClass(QObject* parent) : QObject(parent) + * { + * cuteLogger->registerAppender(new FileAppender("custom_category_log")); + * LOG_TRACE() << "Trace to the custom category log"; + * } + * \endcode + * + * If used compiler supports C++11 standard, LOG_CATEGORY and LOG_GLOBAL_CATEGORY macros would also work when added + * inside of any scope. It could be useful, for example, to log every single run of a method to a different file. + * + * \code + * void foo() + * { + * QString categoryName = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss-zzz"); + * LOG_CATEGORY(categoryName); + * cuteLogger->registerAppender(new FileAppender(categoryName + ".log")); + * ... + * } + * \endcode + * + * \sa Logger::write() + * \sa LOG_TRACE + * \sa Logger::registerCategoryAppender() + * \sa Logger::setDefaultCategory() + * \sa LOG_GLOBAL_CATEGORY + */ + + +/** + * \def LOG_GLOBAL_CATEGORY(category) + * + * \brief Create logger instance inside your custom class to log all messages both to the specified category and to + * the global logger instance. + * + * This macro is similar to LOG_CATEGORY(), but also passes all log messages to the global logger instance appenders. + * It is equal to defining the local category logger using LOG_CATEGORY macro and calling: + * \code cuteLogger->logToGlobalInstance(cuteLogger->defaultCategory(), true); \endcode + * + * \sa LOG_CATEGORY + * \sa Logger::logToGlobalInstance() + * \sa Logger::defaultCategory() + * \sa Logger::registerCategoryAppender() + * \sa Logger::write() + */ + + + +/** + * \def LOG_ASSERT + * + * \brief Check the assertion + * + * This macro is a convinient and recommended to use way to call Logger::writeAssert() function. It uses the + * preprocessor macros (as the LOG_DEBUG() does) to fill the necessary arguments of the Logger::writeAssert() call. It + * also uses undocumented but rather mature and stable \c qt_noop() function (which does nothing) when the assertion + * is true. + * + * Example: + * \code + * bool b = checkSomething(); + * ... + * LOG_ASSERT(b == true); + * \endcode + * + * \sa Logger::writeAssert() + */ + + +/** + * \def LOG_TRACE_TIME + * + * \brief Logs the processing time of current function / code block + * + * This macro automagically measures the function or code of block execution time and outputs it as a Logger::Trace + * level log record. + * + * Example: + * \code + * int foo() + * { + * LOG_TRACE_TIME(); + * ... // Do some long operations + * return 0; + * } // Outputs: Function foo finished in