1
0
mirror of https://github.com/juzzlin/Heimer.git synced 2025-06-11 00:05:21 +08:00

Update SimpleLogger

This commit is contained in:
Jussi Lind 2024-07-08 22:52:29 +03:00
parent 5b3615cdae
commit 9ffe90f9da
9 changed files with 694 additions and 268 deletions

View File

@ -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 1.4.0
===== =====

View File

@ -1,7 +1,7 @@
project(SimpleLogger) cmake_minimum_required(VERSION 3.10)
cmake_policy(VERSION 3.10)
cmake_minimum_required(VERSION 2.8.12) project(SimpleLogger)
cmake_policy(VERSION 2.8.12)
option(BUILD_TESTS "Build unit tests" ON) option(BUILD_TESTS "Build unit tests" ON)
@ -12,16 +12,11 @@ if(NOT CMAKE_BUILD_TYPE)
MinSizeRel." FORCE) MinSizeRel." FORCE)
endif() 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") 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} -Wall -Wextra -Wpedantic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden") 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() endif()
set(LIBRARY_NAME "SimpleLogger") set(LIBRARY_NAME "SimpleLogger")

View File

@ -1,31 +1,34 @@
pipeline { pipeline {
agent any agent none
stages { stages {
stage('CMake Debug build') { stage('Matrix Build') {
agent { matrix {
docker { axes {
image 'juzzlin/qt5-18.04:latest' axis {
args '--privileged -t -v $WORKSPACE:/SimpleLogger' name 'IMAGE'
values 'qt5-20.04', 'qt6-22.04', 'qt6-24.04'
}
axis {
name 'BUILD_TYPE'
values 'Debug', 'Release'
}
} }
} stages {
steps { stage('Build and Test') {
sh "mkdir -p build-debug" agent any
sh "cd build-debug && cmake -DCMAKE_BUILD_TYPE=Debug .." steps {
sh "cd build-debug && cmake --build . --target all -- -j3 && ctest" script {
} docker.image("juzzlin/${IMAGE}:latest").inside('--privileged -t -v $WORKSPACE:/Argengine') {
} def buildDir = "build-${BUILD_TYPE.toLowerCase()}-${IMAGE}"
stage('CMake Release build') { sh "mkdir -p ${buildDir}"
agent { sh "cd ${buildDir} && cmake -GNinja -DCMAKE_BUILD_TYPE=${BUILD_TYPE} .."
docker { sh "cd ${buildDir} && cmake --build . && ctest"
image 'juzzlin/qt5-18.04:latest' }
args '--privileged -t -v $WORKSPACE:/SimpleLogger' }
}
}
} }
} }
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"
}
} }
} }
} }

View File

@ -7,7 +7,7 @@ Looking for a simple logger for your C++ project? `SimpleLogger` might be for yo
* Based on RAII * Based on RAII
* Configurable level symbols * Configurable level symbols
* Datetime / EPOCH timestamps * Datetime, ISO Datetime, EPOCH, and custom timestamp formats
* Logging levels: `Trace`, `Debug`, `Info`, `Warning`, `Error`, `Fatal` * Logging levels: `Trace`, `Debug`, `Info`, `Warning`, `Error`, `Fatal`
* Log to file and/or console * Log to file and/or console
* Thread-safe * Thread-safe
@ -34,7 +34,7 @@ include_directories(SimpleLogger/src)
Link to the library: Link to the library:
``` ```
target_link_libraries(${YOUR_TARGET_NAME} SimpleLogger) target_link_libraries(${YOUR_TARGET_NAME} SimpleLogger_static)
``` ```
In your code: In your code:
@ -78,7 +78,7 @@ Outputs something like this:
``` ```
using juzzlin::L; using juzzlin::L;
L::init("/tmp/myLog.txt"); L::initialize("/tmp/myLog.txt");
L().info() << "Something happened"; L().info() << "Something happened";
``` ```
@ -88,7 +88,7 @@ L().info() << "Something happened";
``` ```
using juzzlin::L; using juzzlin::L;
L::init("/tmp/myLog.txt"); L::initialize("/tmp/myLog.txt");
L::enableEchoMode(false); L::enableEchoMode(false);
L().info() << "Something happened"; L().info() << "Something happened";
@ -111,6 +111,20 @@ Outputs something like this:
`Sat Oct 13 22:38:42 2018 D: A debug thing happened` `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 ## Set custom level symbols
``` ```
@ -126,14 +140,15 @@ Outputs something like this:
`Sat Oct 13 22:38:42 2018 <DEBUG> A debug thing happened` `Sat Oct 13 22:38:42 2018 <DEBUG> 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; using juzzlin::L;
L::setTimestampMode(L::TimestampMode::EpochMilliseconds, " ## "); L::setTimestampMode(L::TimestampMode::EpochMilliseconds);
L::setTimestampSeparator(" ## ");
L().info() << "Something happened"; L().info() << "Something happened";
``` ```
@ -142,6 +157,23 @@ Outputs something like this:
`1562955750677 ## I: Something happened` `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 ## Set custom output stream
``` ```
@ -153,7 +185,7 @@ L::setStream(L::Level::Info, ssI);
# Requirements # Requirements
C++11 C++17
# Licence # Licence

View File

@ -27,338 +27,398 @@
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
#include <fstream> #include <fstream>
#include <iomanip>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <stdexcept> #include <stdexcept>
#ifdef Q_OS_ANDROID
#include <QDebug>
#endif
namespace juzzlin { namespace juzzlin {
class Logger::Impl class SimpleLogger::Impl
{ {
public: public:
Impl(); Impl();
Impl(const std::string & tag);
~Impl(); ~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 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 setStream(Level level, std::ostream & stream);
static void init(std::string filename, bool append); static void initialize(std::string filename, bool append);
void flush(); void flush();
std::ostringstream & getStream(Logger::Level level); std::ostringstream & prepareStreamForLoggingLevel(SimpleLogger::Level level);
void prefixTimestamp();
private: private:
std::string currentDateTime(std::chrono::time_point<std::chrono::system_clock> 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 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::string m_timestampSeparator;
static std::ofstream m_fout; static std::string m_customTimestampFormat;
using SymbolMap = std::map<Logger::Level, std::string>; static std::ofstream m_fileStream;
using SymbolMap = std::map<SimpleLogger::Level, std::string>;
static SymbolMap m_symbols; static SymbolMap m_symbols;
using StreamMap = std::map<Logger::Level, std::ostream *>; using StreamMap = std::map<SimpleLogger::Level, std::ostream *>;
static StreamMap m_streams; static StreamMap m_streams;
static std::recursive_mutex m_mutex; static std::recursive_mutex m_mutex;
Logger::Level m_activeLevel = Logger::Level::Info; SimpleLogger::Level m_activeLevel = SimpleLogger::Level::Info;
std::lock_guard<std::recursive_mutex> m_lock; std::lock_guard<std::recursive_mutex> 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 // Default level symbols
Logger::Impl::SymbolMap Logger::Impl::m_symbols = { SimpleLogger::Impl::SymbolMap SimpleLogger::Impl::m_symbols = {
{Logger::Level::Trace, "T:"}, { SimpleLogger::Level::Trace, "T:" },
{Logger::Level::Debug, "D:"}, { SimpleLogger::Level::Debug, "D:" },
{Logger::Level::Info, "I:"}, { SimpleLogger::Level::Info, "I:" },
{Logger::Level::Warning, "W:"}, { SimpleLogger::Level::Warning, "W:" },
{Logger::Level::Error, "E:"}, { SimpleLogger::Level::Error, "E:" },
{Logger::Level::Fatal, "F:"} { SimpleLogger::Level::Fatal, "F:" }
}; };
// Default streams // Default streams
Logger::Impl::StreamMap Logger::Impl::m_streams = { SimpleLogger::Impl::StreamMap SimpleLogger::Impl::m_streams = {
{Logger::Level::Trace, &std::cout}, { SimpleLogger::Level::Trace, &std::cout },
{Logger::Level::Debug, &std::cout}, { SimpleLogger::Level::Debug, &std::cout },
{Logger::Level::Info, &std::cout}, { SimpleLogger::Level::Info, &std::cout },
{Logger::Level::Warning, &std::cerr}, { SimpleLogger::Level::Warning, &std::cerr },
{Logger::Level::Error, &std::cerr}, { SimpleLogger::Level::Error, &std::cerr },
{Logger::Level::Fatal, &std::cerr} { SimpleLogger::Level::Fatal, &std::cerr }
}; };
std::recursive_mutex Logger::Impl::m_mutex; std::recursive_mutex SimpleLogger::Impl::m_mutex;
Logger::Impl::Impl() SimpleLogger::Impl::Impl()
: m_lock(Logger::Impl::m_mutex) : 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(); 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; m_activeLevel = level;
Impl::prefixTimestamp(); prefixWithTimestamp();
m_oss << Impl::m_symbols[level] << " "; prefixWithLevelAndTag(level);
return m_oss; 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; m_customTimestampFormat = customTimestampFormat;
Impl::m_timestampSeparator = separator;
} }
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<std::chrono::system_clock> 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::duration_cast;
using std::chrono::system_clock; using std::chrono::system_clock;
switch (Impl::m_timestampMode) switch (m_timestampMode) {
{ case SimpleLogger::TimestampMode::None:
case Logger::TimestampMode::None:
break; break;
case Logger::TimestampMode::DateTime: case SimpleLogger::TimestampMode::DateTime: {
{ timestamp = currentDateTime(system_clock::now(), "%a %b %e %H:%M:%S %Y");
time_t rawTime; } break;
time(&rawTime); case SimpleLogger::TimestampMode::ISODateTime: {
timeStr = ctime(&rawTime); timestamp = currentDateTime(system_clock::now(), "%Y-%m-%dT%H:%M:%S");
timeStr.erase(timeStr.length() - 1); } break;
} case SimpleLogger::TimestampMode::EpochSeconds:
timestamp = std::to_string(duration_cast<std::chrono::seconds>(system_clock::now().time_since_epoch()).count());
break; break;
case Logger::TimestampMode::EpochSeconds: case SimpleLogger::TimestampMode::EpochMilliseconds:
timeStr = std::to_string(duration_cast<std::chrono::seconds>(system_clock::now().time_since_epoch()).count()); using std::chrono::duration_cast;
timestamp = std::to_string(duration_cast<std::chrono::milliseconds>(system_clock::now().time_since_epoch()).count());
break; break;
case Logger::TimestampMode::EpochMilliseconds: case SimpleLogger::TimestampMode::EpochMicroseconds:
timeStr = std::to_string(duration_cast<std::chrono::milliseconds>(system_clock::now().time_since_epoch()).count()); using std::chrono::duration_cast;
timestamp = std::to_string(duration_cast<std::chrono::microseconds>(system_clock::now().time_since_epoch()).count());
break; break;
case Logger::TimestampMode::EpochMicroseconds: case SimpleLogger::TimestampMode::Custom:
timeStr = std::to_string(duration_cast<std::chrono::microseconds>(system_clock::now().time_since_epoch()).count()); timestamp = currentDateTime(system_clock::now(), m_customTimestampFormat);
break; break;
} }
if (!timeStr.empty()) if (!timestamp.empty()) {
{ m_message << timestamp << m_timestampSeparator;
m_oss << timeStr << m_timestampSeparator;
} }
} }
void Logger::Impl::flush() bool SimpleLogger::Impl::shouldFlush() const
{ {
if (m_activeLevel < m_level) return m_activeLevel >= m_level && !m_message.str().empty();
{ }
return;
}
if (!m_oss.str().size()) void SimpleLogger::Impl::flushFileIfOpen()
{ {
return; if (m_fileStream.is_open()) {
m_fileStream << m_message.str() << std::endl;
m_fileStream.flush();
} }
}
if (Impl::m_fout.is_open()) void SimpleLogger::Impl::flushEchoIfEnabled()
{ {
Impl::m_fout << m_oss.str() << std::endl; if (m_echoMode) {
Impl::m_fout.flush(); if (auto && stream = m_streams[m_activeLevel]; stream) {
} *stream << m_message.str() << std::endl;
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;
stream->flush(); stream->flush();
} }
#endif
} }
} }
void Logger::Impl::init(std::string filename, bool append) void SimpleLogger::Impl::flush()
{ {
if (!filename.empty()) if (shouldFlush()) {
{ flushFileIfOpen();
Impl::m_fout.open(filename, append ? std::ofstream::out | std::ofstream::app : std::ofstream::out); flushEchoIfEnabled();
if (!Impl::m_fout.is_open()) }
{ }
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"); 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() SimpleLogger::SimpleLogger()
: m_impl(new Logger::Impl) : m_impl(std::make_unique<SimpleLogger::Impl>())
{ {
} }
void Logger::init(std::string filename, bool append) SimpleLogger::SimpleLogger(const std::string & tag)
: m_impl(std::make_unique<SimpleLogger::Impl>(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); Impl::enableEchoMode(enable);
} }
void Logger::setLoggingLevel(Level level) void SimpleLogger::setLoggingLevel(Level level)
{ {
Impl::setLoggingLevel(level); Impl::setLoggingLevel(level);
} }
void Logger::setLevelSymbol(Level level, std::string symbol) void SimpleLogger::setLevelSymbol(Level level, std::string symbol)
{ {
Impl::setLevelSymbol(level, 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); 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 } // juzzlin

View File

@ -22,10 +22,9 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
#ifndef JUZZLIN_LOGGER_HPP #ifndef JUZZLIN_SIMPLE_LOGGER_HPP
#define JUZZLIN_LOGGER_HPP #define JUZZLIN_SIMPLE_LOGGER_HPP
#include <cstdio>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
@ -36,14 +35,14 @@ namespace juzzlin {
* *
* using juzzlin::L; * using juzzlin::L;
* *
* L::init("myLog.txt"); * L::initialize("myLog.txt");
* *
* Example logging: * Example logging:
* *
* L().info() << "Initialization finished."; * L().info() << "Initialization finished.";
* L().error() << "Foo happened!"; * L().error() << "Foo happened!";
*/ */
class Logger class SimpleLogger
{ {
public: public:
enum class Level enum class Level
@ -63,20 +62,26 @@ public:
DateTime, DateTime,
EpochSeconds, EpochSeconds,
EpochMilliseconds, EpochMilliseconds,
EpochMicroseconds EpochMicroseconds,
ISODateTime,
Custom
}; };
//! Constructor. //! Constructor.
Logger(); SimpleLogger();
//! Constructor.
//! \param tag Tag that will be added to the message.
SimpleLogger(const std::string & tag);
//! Destructor. //! Destructor.
~Logger(); ~SimpleLogger();
/*! Initialize the logger. /*! Initialize the logger.
* \param filename Log to filename. Disabled if empty. * \param filename Log to filename. Disabled if empty.
* \param append The existing log will be appended if true. * \param append The existing log will be appended if true.
* Throws on error. */ * 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. //! Enable/disable echo mode.
//! \param enable Echo everything if true. Default is false. //! \param enable Echo everything if true. Default is false.
@ -93,8 +98,15 @@ public:
//! Set/enable timestamp mode. //! Set/enable timestamp mode.
//! \param timestampMode Timestamp mode enumeration. //! \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. //! \param separator Separator string outputted after timestamp.
static void setTimestampMode(TimestampMode timestampMode, std::string separator = " "); static void setTimestampSeparator(std::string separator);
//! Set specific stream. //! Set specific stream.
//! \param level The level. //! \param level The level.
@ -123,15 +135,15 @@ public:
std::ostringstream & fatal(); std::ostringstream & fatal();
private: private:
Logger(const Logger & r) = delete; SimpleLogger(const SimpleLogger &) = delete;
Logger & operator=(const Logger & r) = delete; SimpleLogger & operator=(const SimpleLogger &) = delete;
class Impl; class Impl;
std::unique_ptr<Impl> m_impl; std::unique_ptr<Impl> m_impl;
}; };
using L = Logger; using L = SimpleLogger;
} // juzzlin } // namespace juzzlin
#endif // JUZZLIN_LOGGER_HPP #endif // JUZZLIN_SIMPLE_LOGGER_HPP

View File

@ -22,30 +22,38 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
#include "simple_logger.hpp" #include "../../simple_logger.hpp"
// Don't compile asserts away // Don't compile asserts away
#ifdef NDEBUG #ifdef NDEBUG
#undef NDEBUG #undef NDEBUG
#endif #endif
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <fstream> #include <fstream>
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); assert(lines == expectedLines);
L::enableEchoMode(true); }
L::setLoggingLevel(L::Level::Trace);
void testAllLoggingLevels_allMessagesShouldBeFoundInFile(const std::string & logFileName, const std::string & timestampSeparator)
{
const std::string message = "Hello, world!"; const std::string message = "Hello, world!";
const std::string timestampSeparator = " ## ";
L::setTimestampMode(L::TimestampMode::DateTime, timestampSeparator);
L().trace() << message; L().trace() << message;
L().debug() << message; L().debug() << message;
@ -54,19 +62,28 @@ int main(int, char **)
L().error() << message; L().error() << message;
L().fatal() << message; L().fatal() << message;
std::ifstream fin{logFile}; verifyLogFile(logFileName, message, timestampSeparator, 6);
assert(fin.is_open()); }
int lines = 0; void initializeLoggger(const std::string & logFileName, const std::string & timestampSeparator)
std::string line; {
while (std::getline(fin, line)) L::initialize(logFileName);
{ L::enableEchoMode(true);
assert(line.find(message) != std::string::npos); L::setLoggingLevel(L::Level::Trace);
assert(line.find(timestampSeparator) != std::string::npos); L::setTimestampMode(L::TimestampMode::DateTime);
lines++; 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; return EXIT_SUCCESS;
} }

View File

@ -22,87 +22,377 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
#include "simple_logger.hpp" #include "../../simple_logger.hpp"
// Don't compile asserts away // Don't compile asserts away
#ifdef NDEBUG #ifdef NDEBUG
#undef NDEBUG #undef NDEBUG
#endif #endif
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <iostream>
#include <regex>
#include <sstream> #include <sstream>
#include <stdexcept>
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); if (stream.str().find(message) == std::string::npos) {
assert(stream.str().find(timestampSeparator) != 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); assertString(stream, message);
assert(stream.str().find(timestampSeparator) == std::string::npos); 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); void assertNotMessage(std::stringstream & stream, const std::string & message, const std::string & timestampSeparator)
{
const std::string message = "Hello, world!"; assertNotString(stream, message);
const std::string timestampSeparator = " ## "; assertNotString(stream, timestampSeparator);
L::setTimestampMode(L::TimestampMode::DateTime, timestampSeparator); }
void testFatal_noneLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator)
{
L::setLoggingLevel(L::Level::None); L::setLoggingLevel(L::Level::None);
std::stringstream ssF; std::stringstream ss;
L::setStream(L::Level::Fatal, ssF); L::setStream(L::Level::Fatal, ss);
L().fatal() << message; 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::setLoggingLevel(L::Level::Fatal);
L().fatal() << message; L().fatal() << message;
assertMessage(ssF, message, timestampSeparator); assertMessage(ss, message, timestampSeparator);
}
std::stringstream ssE; void testError_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator)
L::setStream(L::Level::Error, ssE); {
L::setLoggingLevel(L::Level::Fatal);
std::stringstream ss;
L::setStream(L::Level::Error, ss);
L().error() << message; 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::setLoggingLevel(L::Level::Error);
L().error() << message; L().error() << message;
assertMessage(ssE, message, timestampSeparator); assertMessage(ss, message, timestampSeparator);
}
std::stringstream ssW; void testWarning_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator)
L::setStream(L::Level::Warning, ssW); {
L::setLoggingLevel(L::Level::Error);
std::stringstream ss;
L::setStream(L::Level::Warning, ss);
L().warning() << message; 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::setLoggingLevel(L::Level::Warning);
L().warning() << message; L().warning() << message;
assertMessage(ssW, message, timestampSeparator); assertMessage(ss, message, timestampSeparator);
}
std::stringstream ssI; void testInfo_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator)
L::setStream(L::Level::Info, ssI); {
L::setLoggingLevel(L::Level::Warning);
std::stringstream ss;
L::setStream(L::Level::Info, ss);
L().info() << message; 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::setLoggingLevel(L::Level::Info);
L().info() << message; L().info() << message;
assertMessage(ssI, message, timestampSeparator); assertMessage(ss, message, timestampSeparator);
}
std::stringstream ssD; void testDebug_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator)
L::setStream(L::Level::Debug, ssD); {
L::setLoggingLevel(L::Level::Info);
std::stringstream ss;
L::setStream(L::Level::Debug, ss);
L().debug() << message; 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::setLoggingLevel(L::Level::Debug);
L().debug() << message; L().debug() << message;
assertMessage(ssD, message, timestampSeparator); assertMessage(ss, message, timestampSeparator);
}
std::stringstream ssT; void testTrace_higherLoggingLevel_shouldNotPrintMessage(const std::string & message, const std::string & timestampSeparator)
L::setStream(L::Level::Trace, ssT); {
L::setLoggingLevel(L::Level::Debug);
std::stringstream ss;
L::setStream(L::Level::Trace, ss);
L().trace() << message; 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::setLoggingLevel(L::Level::Trace);
L().trace() << message; 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; return EXIT_SUCCESS;
} }

View File

@ -35,9 +35,10 @@ static void initLogger()
using juzzlin::L; using juzzlin::L;
const QString logPath { QDir::tempPath() + QDir::separator() + "heimer-" + std::to_string(Utils::tsMs()).c_str() + ".log" }; 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::enableEchoMode(true);
L::setTimestampMode(L::TimestampMode::DateTime, " "); L::setTimestampMode(L::TimestampMode::ISODateTime);
L::setTimestampSeparator(" ");
const std::map<L::Level, std::string> symbols = { const std::map<L::Level, std::string> symbols = {
{ L::Level::Debug, "D" }, { L::Level::Debug, "D" },
{ L::Level::Error, "E" }, { L::Level::Error, "E" },