From 9ffe90f9da7f016192f81b27fecee65cd2cf1d3e Mon Sep 17 00:00:00 2001 From: Jussi Lind Date: Mon, 8 Jul 2024 22:52:29 +0300 Subject: [PATCH] Update SimpleLogger --- src/contrib/SimpleLogger/CHANGELOG | 16 + src/contrib/SimpleLogger/CMakeLists.txt | 13 +- src/contrib/SimpleLogger/Jenkinsfile | 49 +-- src/contrib/SimpleLogger/README.md | 48 ++- .../SimpleLogger/src/simple_logger.cpp | 362 ++++++++++------- .../SimpleLogger/src/simple_logger.hpp | 42 +- .../src/tests/file_test/file_test.cpp | 59 ++- .../src/tests/stream_test/stream_test.cpp | 368 ++++++++++++++++-- src/main.cpp | 5 +- 9 files changed, 694 insertions(+), 268 deletions(-) diff --git a/src/contrib/SimpleLogger/CHANGELOG b/src/contrib/SimpleLogger/CHANGELOG index 74229d9..b17fbf5 100644 --- a/src/contrib/SimpleLogger/CHANGELOG +++ b/src/contrib/SimpleLogger/CHANGELOG @@ -1,3 +1,19 @@ +2.0.0 +===== + +New features: + +* Fix GitHub Issue #5: Add support for tags +* Add support for customized timestamp format +* Add ISODateTime option to TimestampMode + +Other: + +* Add SimpleLogger::setTimestampSeparator() +* Rename init() to initialize() +* Rename Logger to SimpleLogger +* Require C++17 + 1.4.0 ===== diff --git a/src/contrib/SimpleLogger/CMakeLists.txt b/src/contrib/SimpleLogger/CMakeLists.txt index cbb12f0..13dc256 100644 --- a/src/contrib/SimpleLogger/CMakeLists.txt +++ b/src/contrib/SimpleLogger/CMakeLists.txt @@ -1,7 +1,7 @@ -project(SimpleLogger) +cmake_minimum_required(VERSION 3.10) +cmake_policy(VERSION 3.10) -cmake_minimum_required(VERSION 2.8.12) -cmake_policy(VERSION 2.8.12) +project(SimpleLogger) option(BUILD_TESTS "Build unit tests" ON) @@ -12,16 +12,11 @@ if(NOT CMAKE_BUILD_TYPE) MinSizeRel." FORCE) endif() -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) if(CMAKE_COMPILER_IS_GNUCXX OR MINGW OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden") - - # CMAKE_CXX_STANDARD supported only by versions >= 3.1 - if (CMAKE_VERSION VERSION_LESS "3.1") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") - endif () endif() set(LIBRARY_NAME "SimpleLogger") diff --git a/src/contrib/SimpleLogger/Jenkinsfile b/src/contrib/SimpleLogger/Jenkinsfile index bdc15cf..b27c3a9 100644 --- a/src/contrib/SimpleLogger/Jenkinsfile +++ b/src/contrib/SimpleLogger/Jenkinsfile @@ -1,31 +1,34 @@ pipeline { - agent any + agent none stages { - stage('CMake Debug build') { - agent { - docker { - image 'juzzlin/qt5-18.04:latest' - args '--privileged -t -v $WORKSPACE:/SimpleLogger' + stage('Matrix Build') { + matrix { + axes { + axis { + name 'IMAGE' + values 'qt5-20.04', 'qt6-22.04', 'qt6-24.04' + } + axis { + name 'BUILD_TYPE' + values 'Debug', 'Release' + } } - } - steps { - sh "mkdir -p build-debug" - sh "cd build-debug && cmake -DCMAKE_BUILD_TYPE=Debug .." - sh "cd build-debug && cmake --build . --target all -- -j3 && ctest" - } - } - stage('CMake Release build') { - agent { - docker { - image 'juzzlin/qt5-18.04:latest' - args '--privileged -t -v $WORKSPACE:/SimpleLogger' + stages { + stage('Build and Test') { + agent any + steps { + script { + docker.image("juzzlin/${IMAGE}:latest").inside('--privileged -t -v $WORKSPACE:/Argengine') { + def buildDir = "build-${BUILD_TYPE.toLowerCase()}-${IMAGE}" + sh "mkdir -p ${buildDir}" + sh "cd ${buildDir} && cmake -GNinja -DCMAKE_BUILD_TYPE=${BUILD_TYPE} .." + sh "cd ${buildDir} && cmake --build . && ctest" + } + } + } + } } } - steps { - sh "mkdir -p build-release" - sh "cd build-release && cmake -DCMAKE_BUILD_TYPE=Release .." - sh "cd build-release && cmake --build . --target all -- -j3 && ctest" - } } } } diff --git a/src/contrib/SimpleLogger/README.md b/src/contrib/SimpleLogger/README.md index b83b897..7144850 100644 --- a/src/contrib/SimpleLogger/README.md +++ b/src/contrib/SimpleLogger/README.md @@ -7,7 +7,7 @@ Looking for a simple logger for your C++ project? `SimpleLogger` might be for yo * Based on RAII * Configurable level symbols -* Datetime / EPOCH timestamps +* Datetime, ISO Datetime, EPOCH, and custom timestamp formats * Logging levels: `Trace`, `Debug`, `Info`, `Warning`, `Error`, `Fatal` * Log to file and/or console * Thread-safe @@ -34,7 +34,7 @@ include_directories(SimpleLogger/src) Link to the library: ``` -target_link_libraries(${YOUR_TARGET_NAME} SimpleLogger) +target_link_libraries(${YOUR_TARGET_NAME} SimpleLogger_static) ``` In your code: @@ -78,7 +78,7 @@ Outputs something like this: ``` using juzzlin::L; -L::init("/tmp/myLog.txt"); +L::initialize("/tmp/myLog.txt"); L().info() << "Something happened"; ``` @@ -88,7 +88,7 @@ L().info() << "Something happened"; ``` using juzzlin::L; -L::init("/tmp/myLog.txt"); +L::initialize("/tmp/myLog.txt"); L::enableEchoMode(false); L().info() << "Something happened"; @@ -111,6 +111,20 @@ Outputs something like this: `Sat Oct 13 22:38:42 2018 D: A debug thing happened` +## Log with a tag + +``` +using juzzlin::L; + +L::setLoggingLevel(L::Level::Info); + +L("MyTag").info() << "Something happened"; +``` + +Outputs something like this: + +`Sat Oct 13 22:38:42 2018 I: MyTag: Something happened` + ## Set custom level symbols ``` @@ -126,14 +140,15 @@ Outputs something like this: `Sat Oct 13 22:38:42 2018 A debug thing happened` -## Set timestamp mode and optional custom separator +## Set timestamp mode and optional timestamp separator -Possible modes: `None`, `EpochSeconds`, `EpochMilliseconds`, `EpochMicroseconds`, `DateTime`. +Possible timestamp modes: `None`, `EpochSeconds`, `EpochMilliseconds`, `EpochMicroseconds`, `DateTime`, `ISODateTime`. ``` using juzzlin::L; -L::setTimestampMode(L::TimestampMode::EpochMilliseconds, " ## "); +L::setTimestampMode(L::TimestampMode::EpochMilliseconds); +L::setTimestampSeparator(" ## "); L().info() << "Something happened"; ``` @@ -142,6 +157,23 @@ Outputs something like this: `1562955750677 ## I: Something happened` +## Set custom timestamp format + +By setting a custom timestamp format the timestamp mode is set to `Custom`: + +``` +using juzzlin::L; + +L::setCustomTimestampFormat("%H:%M:%S_%Y-%m-%d"); +L::setTimestampSeparator(" ## "); + +L().info() << "Something happened"; +``` + +Outputs something like this: + +`12:34:58_2024-07-06 ## I: Something happened` + ## Set custom output stream ``` @@ -153,7 +185,7 @@ L::setStream(L::Level::Info, ssI); # Requirements -C++11 +C++17 # Licence diff --git a/src/contrib/SimpleLogger/src/simple_logger.cpp b/src/contrib/SimpleLogger/src/simple_logger.cpp index 109ff97..4dd1505 100644 --- a/src/contrib/SimpleLogger/src/simple_logger.cpp +++ b/src/contrib/SimpleLogger/src/simple_logger.cpp @@ -27,338 +27,398 @@ #include #include #include +#include #include #include #include #include -#ifdef Q_OS_ANDROID -#include -#endif - namespace juzzlin { -class Logger::Impl +class SimpleLogger::Impl { public: - Impl(); + Impl(const std::string & tag); + ~Impl(); - std::ostringstream & trace(); + std::ostringstream & traceStream(); - std::ostringstream & debug(); + std::ostringstream & debugStream(); - std::ostringstream & info(); + std::ostringstream & infoStream(); - std::ostringstream & warning(); + std::ostringstream & warningStream(); - std::ostringstream & error(); + std::ostringstream & errorStream(); - std::ostringstream & fatal(); + std::ostringstream & fatalStream(); static void enableEchoMode(bool enable); - static void setLevelSymbol(Logger::Level level, std::string symbol); + static void setLevelSymbol(SimpleLogger::Level level, std::string symbol); - static void setLoggingLevel(Logger::Level level); + static void setLoggingLevel(SimpleLogger::Level level); - static void setTimestampMode(Logger::TimestampMode timestampMode, std::string separator); + static void setCustomTimestampFormat(std::string format); + + static void setTimestampMode(SimpleLogger::TimestampMode timestampMode); + + static void setTimestampSeparator(std::string separator); static void setStream(Level level, std::ostream & stream); - static void init(std::string filename, bool append); + static void initialize(std::string filename, bool append); void flush(); - std::ostringstream & getStream(Logger::Level level); - - void prefixTimestamp(); + std::ostringstream & prepareStreamForLoggingLevel(SimpleLogger::Level level); private: + std::string currentDateTime(std::chrono::time_point now, const std::string & dateTimeFormat) const; + + void flushFileIfOpen(); + + void flushEchoIfEnabled(); + + void prefixWithLevelAndTag(SimpleLogger::Level level); + + void prefixWithTimestamp(); + + bool shouldFlush() const; static bool m_echoMode; - static Logger::Level m_level; + static SimpleLogger::Level m_level; - static Logger::TimestampMode m_timestampMode; + static SimpleLogger::TimestampMode m_timestampMode; static std::string m_timestampSeparator; - static std::ofstream m_fout; + static std::string m_customTimestampFormat; - using SymbolMap = std::map; + static std::ofstream m_fileStream; + + using SymbolMap = std::map; static SymbolMap m_symbols; - using StreamMap = std::map; + using StreamMap = std::map; static StreamMap m_streams; static std::recursive_mutex m_mutex; - Logger::Level m_activeLevel = Logger::Level::Info; + SimpleLogger::Level m_activeLevel = SimpleLogger::Level::Info; std::lock_guard m_lock; - std::ostringstream m_oss; + std::string m_tag; + + std::ostringstream m_message; }; -bool Logger::Impl::m_echoMode = true; +bool SimpleLogger::Impl::m_echoMode = true; -Logger::Level Logger::Impl::m_level = Logger::Level::Info; +SimpleLogger::Level SimpleLogger::Impl::m_level = SimpleLogger::Level::Info; -Logger::TimestampMode Logger::Impl::m_timestampMode = Logger::TimestampMode::DateTime; +SimpleLogger::TimestampMode SimpleLogger::Impl::m_timestampMode = SimpleLogger::TimestampMode::DateTime; -std::string Logger::Impl::m_timestampSeparator = ": "; +std::string SimpleLogger::Impl::m_timestampSeparator = ": "; -std::ofstream Logger::Impl::m_fout; +std::string SimpleLogger::Impl::m_customTimestampFormat; + +std::ofstream SimpleLogger::Impl::m_fileStream; // Default level symbols -Logger::Impl::SymbolMap Logger::Impl::m_symbols = { - {Logger::Level::Trace, "T:"}, - {Logger::Level::Debug, "D:"}, - {Logger::Level::Info, "I:"}, - {Logger::Level::Warning, "W:"}, - {Logger::Level::Error, "E:"}, - {Logger::Level::Fatal, "F:"} +SimpleLogger::Impl::SymbolMap SimpleLogger::Impl::m_symbols = { + { SimpleLogger::Level::Trace, "T:" }, + { SimpleLogger::Level::Debug, "D:" }, + { SimpleLogger::Level::Info, "I:" }, + { SimpleLogger::Level::Warning, "W:" }, + { SimpleLogger::Level::Error, "E:" }, + { SimpleLogger::Level::Fatal, "F:" } }; // Default streams -Logger::Impl::StreamMap Logger::Impl::m_streams = { - {Logger::Level::Trace, &std::cout}, - {Logger::Level::Debug, &std::cout}, - {Logger::Level::Info, &std::cout}, - {Logger::Level::Warning, &std::cerr}, - {Logger::Level::Error, &std::cerr}, - {Logger::Level::Fatal, &std::cerr} +SimpleLogger::Impl::StreamMap SimpleLogger::Impl::m_streams = { + { SimpleLogger::Level::Trace, &std::cout }, + { SimpleLogger::Level::Debug, &std::cout }, + { SimpleLogger::Level::Info, &std::cout }, + { SimpleLogger::Level::Warning, &std::cerr }, + { SimpleLogger::Level::Error, &std::cerr }, + { SimpleLogger::Level::Fatal, &std::cerr } }; -std::recursive_mutex Logger::Impl::m_mutex; +std::recursive_mutex SimpleLogger::Impl::m_mutex; -Logger::Impl::Impl() - : m_lock(Logger::Impl::m_mutex) +SimpleLogger::Impl::Impl() + : m_lock(m_mutex) { } -Logger::Impl::~Impl() +SimpleLogger::Impl::Impl(const std::string & tag) + : m_lock(m_mutex) + , m_tag(tag) +{ +} + +SimpleLogger::Impl::~Impl() { flush(); } -void Logger::Impl::enableEchoMode(bool enable) +void SimpleLogger::Impl::enableEchoMode(bool enable) { - Impl::m_echoMode = enable; + m_echoMode = enable; } -std::ostringstream & Logger::Impl::getStream(Logger::Level level) +std::ostringstream & SimpleLogger::Impl::prepareStreamForLoggingLevel(SimpleLogger::Level level) { m_activeLevel = level; - Impl::prefixTimestamp(); - m_oss << Impl::m_symbols[level] << " "; - return m_oss; + prefixWithTimestamp(); + prefixWithLevelAndTag(level); + return m_message; } -void Logger::Impl::setLevelSymbol(Level level, std::string symbol) +void SimpleLogger::Impl::setLevelSymbol(Level level, std::string symbol) { - Impl::m_symbols[level] = symbol; + m_symbols[level] = symbol; } -void Logger::Impl::setLoggingLevel(Logger::Level level) +void SimpleLogger::Impl::setLoggingLevel(SimpleLogger::Level level) { - Impl::m_level = level; + m_level = level; } -void Logger::Impl::setTimestampMode(TimestampMode timestampMode, std::string separator) +void SimpleLogger::Impl::setCustomTimestampFormat(std::string customTimestampFormat) { - Impl::m_timestampMode = timestampMode; - Impl::m_timestampSeparator = separator; + m_customTimestampFormat = customTimestampFormat; } -void Logger::Impl::prefixTimestamp() +void SimpleLogger::Impl::setTimestampMode(TimestampMode timestampMode) { - std::string timeStr; + m_timestampMode = timestampMode; +} + +void SimpleLogger::Impl::setTimestampSeparator(std::string separator) +{ + m_timestampSeparator = separator; +} + +std::string SimpleLogger::Impl::currentDateTime(std::chrono::time_point now, const std::string & dateTimeFormat) const +{ + std::ostringstream oss; + const auto rawTime = std::chrono::system_clock::to_time_t(now); + oss << std::put_time(std::localtime(&rawTime), dateTimeFormat.c_str()); + + return oss.str(); +} + +void SimpleLogger::Impl::prefixWithLevelAndTag(SimpleLogger::Level level) +{ + m_message << m_symbols[level] << (!m_tag.empty() ? " " + m_tag + ":" : "") << " "; +} + +void SimpleLogger::Impl::prefixWithTimestamp() +{ + std::string timestamp; using std::chrono::duration_cast; using std::chrono::system_clock; - switch (Impl::m_timestampMode) - { - case Logger::TimestampMode::None: + switch (m_timestampMode) { + case SimpleLogger::TimestampMode::None: break; - case Logger::TimestampMode::DateTime: - { - time_t rawTime; - time(&rawTime); - timeStr = ctime(&rawTime); - timeStr.erase(timeStr.length() - 1); - } + case SimpleLogger::TimestampMode::DateTime: { + timestamp = currentDateTime(system_clock::now(), "%a %b %e %H:%M:%S %Y"); + } break; + case SimpleLogger::TimestampMode::ISODateTime: { + timestamp = currentDateTime(system_clock::now(), "%Y-%m-%dT%H:%M:%S"); + } break; + case SimpleLogger::TimestampMode::EpochSeconds: + timestamp = std::to_string(duration_cast(system_clock::now().time_since_epoch()).count()); break; - case Logger::TimestampMode::EpochSeconds: - timeStr = std::to_string(duration_cast(system_clock::now().time_since_epoch()).count()); + case SimpleLogger::TimestampMode::EpochMilliseconds: + using std::chrono::duration_cast; + timestamp = std::to_string(duration_cast(system_clock::now().time_since_epoch()).count()); break; - case Logger::TimestampMode::EpochMilliseconds: - timeStr = std::to_string(duration_cast(system_clock::now().time_since_epoch()).count()); + case SimpleLogger::TimestampMode::EpochMicroseconds: + using std::chrono::duration_cast; + timestamp = std::to_string(duration_cast(system_clock::now().time_since_epoch()).count()); break; - case Logger::TimestampMode::EpochMicroseconds: - timeStr = std::to_string(duration_cast(system_clock::now().time_since_epoch()).count()); + case SimpleLogger::TimestampMode::Custom: + timestamp = currentDateTime(system_clock::now(), m_customTimestampFormat); break; } - if (!timeStr.empty()) - { - m_oss << timeStr << m_timestampSeparator; + if (!timestamp.empty()) { + m_message << timestamp << m_timestampSeparator; } } -void Logger::Impl::flush() +bool SimpleLogger::Impl::shouldFlush() const { - if (m_activeLevel < m_level) - { - return; - } + return m_activeLevel >= m_level && !m_message.str().empty(); +} - if (!m_oss.str().size()) - { - return; +void SimpleLogger::Impl::flushFileIfOpen() +{ + if (m_fileStream.is_open()) { + m_fileStream << m_message.str() << std::endl; + m_fileStream.flush(); } +} - if (Impl::m_fout.is_open()) - { - Impl::m_fout << m_oss.str() << std::endl; - Impl::m_fout.flush(); - } - - if (Impl::m_echoMode) - { -#ifdef Q_OS_ANDROID - qDebug() << m_oss.str().c_str(); -#else - auto stream = Impl::m_streams[m_activeLevel]; - if (stream) { - *stream << m_oss.str() << std::endl; +void SimpleLogger::Impl::flushEchoIfEnabled() +{ + if (m_echoMode) { + if (auto && stream = m_streams[m_activeLevel]; stream) { + *stream << m_message.str() << std::endl; stream->flush(); } -#endif } } -void Logger::Impl::init(std::string filename, bool append) +void SimpleLogger::Impl::flush() { - if (!filename.empty()) - { - Impl::m_fout.open(filename, append ? std::ofstream::out | std::ofstream::app : std::ofstream::out); - if (!Impl::m_fout.is_open()) - { + if (shouldFlush()) { + flushFileIfOpen(); + flushEchoIfEnabled(); + } +} + +void SimpleLogger::Impl::initialize(std::string filename, bool append) +{ + if (!filename.empty()) { + m_fileStream.open(filename, append ? std::ofstream::out | std::ofstream::app : std::ofstream::out); + if (!m_fileStream.is_open()) { throw std::runtime_error("ERROR!!: Couldn't open '" + filename + "' for write.\n"); } } } -std::ostringstream & Logger::Impl::trace() +std::ostringstream & SimpleLogger::Impl::traceStream() { - return getStream(Logger::Level::Trace); + return prepareStreamForLoggingLevel(SimpleLogger::Level::Trace); } -std::ostringstream & Logger::Impl::debug() +std::ostringstream & SimpleLogger::Impl::debugStream() { - return getStream(Logger::Level::Debug); + return prepareStreamForLoggingLevel(SimpleLogger::Level::Debug); } -std::ostringstream & Logger::Impl::info() +std::ostringstream & SimpleLogger::Impl::infoStream() { - return getStream(Logger::Level::Info); + return prepareStreamForLoggingLevel(SimpleLogger::Level::Info); } -std::ostringstream & Logger::Impl::warning() +std::ostringstream & SimpleLogger::Impl::warningStream() { - return getStream(Logger::Level::Warning); + return prepareStreamForLoggingLevel(SimpleLogger::Level::Warning); } -std::ostringstream & Logger::Impl::error() +std::ostringstream & SimpleLogger::Impl::errorStream() { - return getStream(Logger::Level::Error); + return prepareStreamForLoggingLevel(SimpleLogger::Level::Error); } -std::ostringstream & Logger::Impl::fatal() +std::ostringstream & SimpleLogger::Impl::fatalStream() { - return getStream(Logger::Level::Fatal); + return prepareStreamForLoggingLevel(SimpleLogger::Level::Fatal); } -void Logger::Impl::setStream(Level level, std::ostream & stream) +void SimpleLogger::Impl::setStream(Level level, std::ostream & stream) { - Logger::Impl::m_streams[level] = &stream; + m_streams[level] = &stream; } -Logger::Logger() - : m_impl(new Logger::Impl) +SimpleLogger::SimpleLogger() + : m_impl(std::make_unique()) { } -void Logger::init(std::string filename, bool append) +SimpleLogger::SimpleLogger(const std::string & tag) + : m_impl(std::make_unique(tag)) { - Impl::init(filename, append); } -void Logger::enableEchoMode(bool enable) +void SimpleLogger::initialize(std::string filename, bool append) +{ + Impl::initialize(filename, append); +} + +void SimpleLogger::enableEchoMode(bool enable) { Impl::enableEchoMode(enable); } -void Logger::setLoggingLevel(Level level) +void SimpleLogger::setLoggingLevel(Level level) { Impl::setLoggingLevel(level); } -void Logger::setLevelSymbol(Level level, std::string symbol) +void SimpleLogger::setLevelSymbol(Level level, std::string symbol) { Impl::setLevelSymbol(level, symbol); } -void Logger::setTimestampMode(TimestampMode timestampMode, std::string separator) +void SimpleLogger::setTimestampMode(TimestampMode timestampMode) { - Impl::setTimestampMode(timestampMode, separator); + Impl::setTimestampMode(timestampMode); } -void Logger::setStream(Level level, std::ostream & stream) +void SimpleLogger::setCustomTimestampFormat(std::string customTimestampFormat) +{ + Impl::setTimestampMode(TimestampMode::Custom); + Impl::setCustomTimestampFormat(customTimestampFormat); +} + +void SimpleLogger::setTimestampSeparator(std::string timestampSeparator) +{ + Impl::setTimestampSeparator(timestampSeparator); +} + +void SimpleLogger::setStream(Level level, std::ostream & stream) { Impl::setStream(level, stream); } -std::ostringstream & Logger::trace() +std::ostringstream & SimpleLogger::trace() { - return m_impl->trace(); + return m_impl->traceStream(); } -std::ostringstream & Logger::debug() +std::ostringstream & SimpleLogger::debug() { - return m_impl->debug(); + return m_impl->debugStream(); } -std::ostringstream & Logger::info() +std::ostringstream & SimpleLogger::info() { - return m_impl->info(); + return m_impl->infoStream(); } -std::ostringstream & Logger::warning() +std::ostringstream & SimpleLogger::warning() { - return m_impl->warning(); + return m_impl->warningStream(); } -std::ostringstream & Logger::error() +std::ostringstream & SimpleLogger::error() { - return m_impl->error(); + return m_impl->errorStream(); } -std::ostringstream & Logger::fatal() +std::ostringstream & SimpleLogger::fatal() { - return m_impl->fatal(); + return m_impl->fatalStream(); } -std::string Logger::version() +std::string SimpleLogger::version() { - return "1.4.0"; + return "2.0.0"; } -Logger::~Logger() = default; +SimpleLogger::~SimpleLogger() = default; } // juzzlin diff --git a/src/contrib/SimpleLogger/src/simple_logger.hpp b/src/contrib/SimpleLogger/src/simple_logger.hpp index 8cdbd87..ffc165d 100644 --- a/src/contrib/SimpleLogger/src/simple_logger.hpp +++ b/src/contrib/SimpleLogger/src/simple_logger.hpp @@ -22,10 +22,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#ifndef JUZZLIN_LOGGER_HPP -#define JUZZLIN_LOGGER_HPP +#ifndef JUZZLIN_SIMPLE_LOGGER_HPP +#define JUZZLIN_SIMPLE_LOGGER_HPP -#include #include #include @@ -36,14 +35,14 @@ namespace juzzlin { * * using juzzlin::L; * - * L::init("myLog.txt"); + * L::initialize("myLog.txt"); * * Example logging: * * L().info() << "Initialization finished."; * L().error() << "Foo happened!"; */ -class Logger +class SimpleLogger { public: enum class Level @@ -63,20 +62,26 @@ public: DateTime, EpochSeconds, EpochMilliseconds, - EpochMicroseconds + EpochMicroseconds, + ISODateTime, + Custom }; //! Constructor. - Logger(); + SimpleLogger(); + + //! Constructor. + //! \param tag Tag that will be added to the message. + SimpleLogger(const std::string & tag); //! Destructor. - ~Logger(); + ~SimpleLogger(); /*! Initialize the logger. * \param filename Log to filename. Disabled if empty. * \param append The existing log will be appended if true. * Throws on error. */ - static void init(std::string filename, bool append = false); + static void initialize(std::string filename, bool append = false); //! Enable/disable echo mode. //! \param enable Echo everything if true. Default is false. @@ -93,8 +98,15 @@ public: //! Set/enable timestamp mode. //! \param timestampMode Timestamp mode enumeration. + static void setTimestampMode(TimestampMode timestampMode); + + //! Set custom timestamp format. Sets timestamp mode to TimestampMode::Custom. + //! \param customTimestampFormat Timestamp format e.g. "%Y-%m-%dT%H:%M:%S". + static void setCustomTimestampFormat(std::string customTimestampFormat); + + //! Set/enable timestamp separator. //! \param separator Separator string outputted after timestamp. - static void setTimestampMode(TimestampMode timestampMode, std::string separator = " "); + static void setTimestampSeparator(std::string separator); //! Set specific stream. //! \param level The level. @@ -123,15 +135,15 @@ public: std::ostringstream & fatal(); private: - Logger(const Logger & r) = delete; - Logger & operator=(const Logger & r) = delete; + SimpleLogger(const SimpleLogger &) = delete; + SimpleLogger & operator=(const SimpleLogger &) = delete; class Impl; std::unique_ptr m_impl; }; -using L = Logger; +using L = SimpleLogger; -} // juzzlin +} // namespace juzzlin -#endif // JUZZLIN_LOGGER_HPP +#endif // JUZZLIN_SIMPLE_LOGGER_HPP diff --git a/src/contrib/SimpleLogger/src/tests/file_test/file_test.cpp b/src/contrib/SimpleLogger/src/tests/file_test/file_test.cpp index c66ec37..30b1cce 100644 --- a/src/contrib/SimpleLogger/src/tests/file_test/file_test.cpp +++ b/src/contrib/SimpleLogger/src/tests/file_test/file_test.cpp @@ -22,30 +22,38 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#include "simple_logger.hpp" +#include "../../simple_logger.hpp" // Don't compile asserts away #ifdef NDEBUG - #undef NDEBUG +#undef NDEBUG #endif #include #include #include -int main(int, char **) +namespace juzzlin::FileTest { + +void verifyLogFile(const std::string & logFile, const std::string & message, const std::string & timestampSeparator, int expectedLines) { - using juzzlin::L; + std::ifstream fin { logFile }; + assert(fin.is_open()); - const auto logFile = "file_test.log"; + int lines = 0; + std::string line; + while (std::getline(fin, line)) { + assert(line.find(message) != std::string::npos); + assert(line.find(timestampSeparator) != std::string::npos); + lines++; + } - L::init(logFile); - L::enableEchoMode(true); - L::setLoggingLevel(L::Level::Trace); + assert(lines == expectedLines); +} +void testAllLoggingLevels_allMessagesShouldBeFoundInFile(const std::string & logFileName, const std::string & timestampSeparator) +{ const std::string message = "Hello, world!"; - const std::string timestampSeparator = " ## "; - L::setTimestampMode(L::TimestampMode::DateTime, timestampSeparator); L().trace() << message; L().debug() << message; @@ -54,19 +62,28 @@ int main(int, char **) L().error() << message; L().fatal() << message; - std::ifstream fin{logFile}; - assert(fin.is_open()); + verifyLogFile(logFileName, message, timestampSeparator, 6); +} - int lines = 0; - std::string line; - while (std::getline(fin, line)) - { - assert(line.find(message) != std::string::npos); - assert(line.find(timestampSeparator) != std::string::npos); - lines++; - } +void initializeLoggger(const std::string & logFileName, const std::string & timestampSeparator) +{ + L::initialize(logFileName); + L::enableEchoMode(true); + L::setLoggingLevel(L::Level::Trace); + L::setTimestampMode(L::TimestampMode::DateTime); + L::setTimestampSeparator(timestampSeparator); +} - assert(lines == 6); +} // namespace juzzlin::FileTest + +int main(int, char **) +{ + const std::string logFileName = "file_test.log"; + const std::string timestampSeparator = " ## "; + + juzzlin::FileTest::initializeLoggger(logFileName, timestampSeparator); + + juzzlin::FileTest::testAllLoggingLevels_allMessagesShouldBeFoundInFile(logFileName, timestampSeparator); return EXIT_SUCCESS; } diff --git a/src/contrib/SimpleLogger/src/tests/stream_test/stream_test.cpp b/src/contrib/SimpleLogger/src/tests/stream_test/stream_test.cpp index 199e592..2eb8a48 100644 --- a/src/contrib/SimpleLogger/src/tests/stream_test/stream_test.cpp +++ b/src/contrib/SimpleLogger/src/tests/stream_test/stream_test.cpp @@ -22,87 +22,377 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#include "simple_logger.hpp" +#include "../../simple_logger.hpp" // Don't compile asserts away #ifdef NDEBUG - #undef NDEBUG +#undef NDEBUG #endif #include #include +#include +#include #include +#include -void assertMessage(std::stringstream & stream, std::string message, std::string timestampSeparator) +namespace juzzlin::StreamTest { + +void assertString(std::stringstream & stream, const std::string & message) { - assert(stream.str().find(message) != std::string::npos); - assert(stream.str().find(timestampSeparator) != std::string::npos); + if (stream.str().find(message) == std::string::npos) { + throw std::runtime_error("ERROR!!: '" + message + "' not found in '" + stream.str() + "'"); + } } -void assertNotMessage(std::stringstream & stream, std::string message, std::string timestampSeparator) +void assertMessage(std::stringstream & stream, const std::string & message, const std::string & timestampSeparator) { - assert(stream.str().find(message) == std::string::npos); - assert(stream.str().find(timestampSeparator) == std::string::npos); + assertString(stream, message); + assertString(stream, timestampSeparator); } -int main(int, char **) +void assertNotString(std::stringstream & stream, const std::string & message) { - using juzzlin::L; + if (stream.str().find(message) != std::string::npos) { + throw std::runtime_error("ERROR!!: '" + message + "' was found in '" + stream.str() + "'"); + } +} - L::enableEchoMode(true); - - const std::string message = "Hello, world!"; - const std::string timestampSeparator = " ## "; - L::setTimestampMode(L::TimestampMode::DateTime, timestampSeparator); +void assertNotMessage(std::stringstream & stream, const std::string & message, const std::string & timestampSeparator) +{ + assertNotString(stream, message); + assertNotString(stream, timestampSeparator); +} +void testFatal_noneLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ L::setLoggingLevel(L::Level::None); - std::stringstream ssF; - L::setStream(L::Level::Fatal, ssF); + std::stringstream ss; + L::setStream(L::Level::Fatal, ss); L().fatal() << message; - assertNotMessage(ssF, message, timestampSeparator); + assertNotMessage(ss, message, timestampSeparator); +} + +void testFatal_fatalLoggingLevel_shouldPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Fatal, ss); L::setLoggingLevel(L::Level::Fatal); L().fatal() << message; - assertMessage(ssF, message, timestampSeparator); + assertMessage(ss, message, timestampSeparator); +} - std::stringstream ssE; - L::setStream(L::Level::Error, ssE); +void testError_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + L::setLoggingLevel(L::Level::Fatal); + std::stringstream ss; + L::setStream(L::Level::Error, ss); L().error() << message; - assertNotMessage(ssE, message, timestampSeparator); + assertNotMessage(ss, message, timestampSeparator); +} + +void testError_errorLoggingLevel_shouldPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Error, ss); L::setLoggingLevel(L::Level::Error); L().error() << message; - assertMessage(ssE, message, timestampSeparator); + assertMessage(ss, message, timestampSeparator); +} - std::stringstream ssW; - L::setStream(L::Level::Warning, ssW); +void testWarning_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + L::setLoggingLevel(L::Level::Error); + std::stringstream ss; + L::setStream(L::Level::Warning, ss); L().warning() << message; - assertNotMessage(ssW, message, timestampSeparator); + assertNotMessage(ss, message, timestampSeparator); +} + +void testWarning_warningLoggingLevel_shouldPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Warning, ss); L::setLoggingLevel(L::Level::Warning); L().warning() << message; - assertMessage(ssW, message, timestampSeparator); + assertMessage(ss, message, timestampSeparator); +} - std::stringstream ssI; - L::setStream(L::Level::Info, ssI); +void testInfo_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + L::setLoggingLevel(L::Level::Warning); + std::stringstream ss; + L::setStream(L::Level::Info, ss); L().info() << message; - assertNotMessage(ssI, message, timestampSeparator); + assertNotMessage(ss, message, timestampSeparator); +} + +void testInfo_infoLoggingLevel_shouldPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Info, ss); L::setLoggingLevel(L::Level::Info); L().info() << message; - assertMessage(ssI, message, timestampSeparator); + assertMessage(ss, message, timestampSeparator); +} - std::stringstream ssD; - L::setStream(L::Level::Debug, ssD); +void testDebug_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + L::setLoggingLevel(L::Level::Info); + std::stringstream ss; + L::setStream(L::Level::Debug, ss); L().debug() << message; - assertNotMessage(ssD, message, timestampSeparator); + assertNotMessage(ss, message, timestampSeparator); +} + +void testDebug_debugLoggingLevel_shouldPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Debug, ss); L::setLoggingLevel(L::Level::Debug); L().debug() << message; - assertMessage(ssD, message, timestampSeparator); + assertMessage(ss, message, timestampSeparator); +} - std::stringstream ssT; - L::setStream(L::Level::Trace, ssT); +void testTrace_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + L::setLoggingLevel(L::Level::Debug); + std::stringstream ss; + L::setStream(L::Level::Trace, ss); L().trace() << message; - assertNotMessage(ssT, message, timestampSeparator); + assertNotMessage(ss, message, timestampSeparator); +} + +void testTrace_traceLoggingLevel_shouldPrintMessage(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Trace, ss); L::setLoggingLevel(L::Level::Trace); L().trace() << message; - assertMessage(ssT, message, timestampSeparator); + assertMessage(ss, message, timestampSeparator); +} + +void testTag_fatalLevel_shouldPrintTag(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Fatal, ss); + L::setLoggingLevel(L::Level::Fatal); + const std::string tag = "TAG"; + L(tag).fatal() << message; + assertMessage(ss, tag + ": " + message, timestampSeparator); +} + +void testTag_errorLevel_shouldPrintTag(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Error, ss); + L::setLoggingLevel(L::Level::Error); + const std::string tag = "TAG"; + L(tag).fatal() << message; + assertMessage(ss, tag + ": " + message, timestampSeparator); +} + +void testTag_warningLevel_shouldPrintTag(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Warning, ss); + L::setLoggingLevel(L::Level::Warning); + const std::string tag = "TAG"; + L(tag).fatal() << message; + assertMessage(ss, tag + ": " + message, timestampSeparator); +} + +void testTag_infoLevel_shouldPrintTag(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Info, ss); + L::setLoggingLevel(L::Level::Info); + const std::string tag = "TAG"; + L(tag).fatal() << message; + assertMessage(ss, tag + ": " + message, timestampSeparator); +} + +void testTag_debugLevel_shouldPrintTag(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Debug, ss); + L::setLoggingLevel(L::Level::Debug); + const std::string tag = "TAG"; + L(tag).fatal() << message; + assertMessage(ss, tag + ": " + message, timestampSeparator); +} + +void testTag_traceLevel_shouldPrintTag(const std::string & message, const std::string & timestampSeparator) +{ + std::stringstream ss; + L::setStream(L::Level::Trace, ss); + L::setLoggingLevel(L::Level::Trace); + const std::string tag = "TAG"; + L(tag).fatal() << message; + assertMessage(ss, tag + ": " + message, timestampSeparator); +} + +void initializeLogger(const std::string & timestampSeparator) +{ + L::enableEchoMode(true); + L::setTimestampMode(L::TimestampMode::DateTime); + L::setTimestampSeparator(timestampSeparator); +} + +void testTimestampMode_none_shouldNotPrintTimestamp(const std::string & message) +{ + L::setTimestampMode(L::TimestampMode::None); + std::stringstream ss; + L::setStream(L::Level::Info, ss); + L::setLoggingLevel(L::Level::Info); + L().info() << message; + assertString(ss, message); + assertNotString(ss, "##"); +} + +void testTimestampMode_dateTime_shouldPrintDateTimeTimestamp(const std::string & message) +{ + L::setTimestampMode(L::TimestampMode::DateTime); + std::stringstream ss; + L::setStream(L::Level::Info, ss); + L().info() << message; + + assert(ss.str().find(message) != std::string::npos); + + // Example of expected format: "Mon Jan 1 00:00:00 2021" + // Regex to match date-time format (handling single-digit day with optional space) + std::regex dateTimeRegex(R"(\w{3} \w{3} \s?\d{1,2} \d{2}:\d{2}:\d{2} \d{4} ##)"); + assert(std::regex_search(ss.str(), dateTimeRegex)); +} + +void testTimestampMode_epochSeconds_shouldPrintEpochSecondsTimestamp(const std::string & message) +{ + L::setTimestampMode(L::TimestampMode::EpochSeconds); + std::stringstream ss; + L::setStream(L::Level::Info, ss); + L().info() << message; + assert(ss.str().find(message) != std::string::npos); + + // Regex to match epoch seconds format + const std::regex epochSecondsRegex(R"(\d{10} ##)"); + assert(std::regex_search(ss.str(), epochSecondsRegex)); +} + +void testTimestampMode_epochMilliseconds_shouldPrintEpochMillisecondsTimestamp(const std::string & message) +{ + L::setTimestampMode(L::TimestampMode::EpochMilliseconds); + std::stringstream ss; + L::setStream(L::Level::Info, ss); + L().info() << message; + assert(ss.str().find(message) != std::string::npos); + + // Regex to match epoch milliseconds format + const std::regex epochMillisecondsRegex(R"(\d{13} ##)"); + assert(std::regex_search(ss.str(), epochMillisecondsRegex)); +} + +void testTimestampMode_epochMicroseconds_shouldPrintEpochMicrosecondsTimestamp(const std::string & message) +{ + L::setTimestampMode(L::TimestampMode::EpochMicroseconds); + std::stringstream ss; + L::setStream(L::Level::Info, ss); + L().info() << message; + assert(ss.str().find(message) != std::string::npos); + + // Regex to match epoch microseconds format + const std::regex epochMicrosecondsRegex(R"(\d{16} ##)"); + assert(std::regex_search(ss.str(), epochMicrosecondsRegex)); +} + +void testTimestampMode_ISODateTime_shouldPrintISODateTimeTimestamp(const std::string & message) +{ + L::setTimestampMode(L::TimestampMode::ISODateTime); + std::stringstream ss; + L::setStream(L::Level::Info, ss); + L().info() << message; + assert(ss.str().find(message) != std::string::npos); + + // Regex to match ISO 8601 date-time format + const std::regex isoDateTimeRegex(R"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d+)?Z? ##)"); + assert(std::regex_search(ss.str(), isoDateTimeRegex)); +} + +void testTimestampMode_Custom_shouldPrintCustomTimestamp(const std::string & message) +{ + L::setCustomTimestampFormat("%H:%M:%S_%Y-%m-%d"); + std::stringstream ss; + L::setStream(L::Level::Info, ss); + L().info() << message; + assert(ss.str().find(message) != std::string::npos); + + // Regex to match custom format + const std::regex isoDateTimeRegex(R"(\d{2}:\d{2}:\d{2}_\d{4}-\d{2}-\d{2} ##)"); + assert(std::regex_search(ss.str(), isoDateTimeRegex)); +} + +void runTests() +{ + const std::string message = "Hello world!"; + const std::string timestampSeparator = " ## "; + + initializeLogger(timestampSeparator); + + testFatal_noneLoggingLevel_shouldNotPrintMessage(message, timestampSeparator); + + testFatal_fatalLoggingLevel_shouldPrintMessage(message, timestampSeparator); + + testError_higherLoggingLevel_shouldNotPrintMessage(message, timestampSeparator); + + testError_errorLoggingLevel_shouldPrintMessage(message, timestampSeparator); + + testWarning_higherLoggingLevel_shouldNotPrintMessage(message, timestampSeparator); + + testWarning_warningLoggingLevel_shouldPrintMessage(message, timestampSeparator); + + testInfo_higherLoggingLevel_shouldNotPrintMessage(message, timestampSeparator); + + testInfo_infoLoggingLevel_shouldPrintMessage(message, timestampSeparator); + + testDebug_higherLoggingLevel_shouldNotPrintMessage(message, timestampSeparator); + + testDebug_debugLoggingLevel_shouldPrintMessage(message, timestampSeparator); + + testTrace_higherLoggingLevel_shouldNotPrintMessage(message, timestampSeparator); + + testTrace_traceLoggingLevel_shouldPrintMessage(message, timestampSeparator); + + testTag_fatalLevel_shouldPrintTag(message, timestampSeparator); + + testTag_errorLevel_shouldPrintTag(message, timestampSeparator); + + testTag_warningLevel_shouldPrintTag(message, timestampSeparator); + + testTag_infoLevel_shouldPrintTag(message, timestampSeparator); + + testTag_debugLevel_shouldPrintTag(message, timestampSeparator); + + testTag_traceLevel_shouldPrintTag(message, timestampSeparator); + + testTimestampMode_none_shouldNotPrintTimestamp(message); + + testTimestampMode_dateTime_shouldPrintDateTimeTimestamp(message); + + testTimestampMode_epochSeconds_shouldPrintEpochSecondsTimestamp(message); + + testTimestampMode_epochMilliseconds_shouldPrintEpochMillisecondsTimestamp(message); + + testTimestampMode_epochMicroseconds_shouldPrintEpochMicrosecondsTimestamp(message); + + testTimestampMode_ISODateTime_shouldPrintISODateTimeTimestamp(message); + + testTimestampMode_Custom_shouldPrintCustomTimestamp(message); +} + +} // namespace juzzlin::StreamTest + +int main() +{ + juzzlin::StreamTest::runTests(); return EXIT_SUCCESS; } diff --git a/src/main.cpp b/src/main.cpp index 59e22a4..02b90de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,9 +35,10 @@ static void initLogger() using juzzlin::L; const QString logPath { QDir::tempPath() + QDir::separator() + "heimer-" + std::to_string(Utils::tsMs()).c_str() + ".log" }; - L::init(logPath.toStdString()); + L::initialize(logPath.toStdString()); L::enableEchoMode(true); - L::setTimestampMode(L::TimestampMode::DateTime, " "); + L::setTimestampMode(L::TimestampMode::ISODateTime); + L::setTimestampSeparator(" "); const std::map symbols = { { L::Level::Debug, "D" }, { L::Level::Error, "E" },