1
0
mirror of https://github.com/juzzlin/Heimer.git synced 2025-06-10 07:37:14 +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
=====

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)
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")

View File

@ -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"
}
}
}
}

View File

@ -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 <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;
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

View File

@ -27,338 +27,398 @@
#include <chrono>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <mutex>
#include <stdexcept>
#ifdef Q_OS_ANDROID
#include <QDebug>
#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<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 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<Logger::Level, std::string>;
static std::ofstream m_fileStream;
using SymbolMap = std::map<SimpleLogger::Level, std::string>;
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 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::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<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::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<std::chrono::seconds>(system_clock::now().time_since_epoch()).count());
break;
case Logger::TimestampMode::EpochSeconds:
timeStr = std::to_string(duration_cast<std::chrono::seconds>(system_clock::now().time_since_epoch()).count());
case SimpleLogger::TimestampMode::EpochMilliseconds:
using std::chrono::duration_cast;
timestamp = std::to_string(duration_cast<std::chrono::milliseconds>(system_clock::now().time_since_epoch()).count());
break;
case Logger::TimestampMode::EpochMilliseconds:
timeStr = std::to_string(duration_cast<std::chrono::milliseconds>(system_clock::now().time_since_epoch()).count());
case SimpleLogger::TimestampMode::EpochMicroseconds:
using std::chrono::duration_cast;
timestamp = std::to_string(duration_cast<std::chrono::microseconds>(system_clock::now().time_since_epoch()).count());
break;
case Logger::TimestampMode::EpochMicroseconds:
timeStr = std::to_string(duration_cast<std::chrono::microseconds>(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<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);
}
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

View File

@ -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 <cstdio>
#include <memory>
#include <sstream>
@ -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<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
// SOFTWARE.
#include "simple_logger.hpp"
#include "../../simple_logger.hpp"
// Don't compile asserts away
#ifdef NDEBUG
#undef NDEBUG
#undef NDEBUG
#endif
#include <cassert>
#include <cstdlib>
#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);
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;
}

View File

@ -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 <cassert>
#include <cstdlib>
#include <iostream>
#include <regex>
#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);
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;
}

View File

@ -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<L::Level, std::string> symbols = {
{ L::Level::Debug, "D" },
{ L::Level::Error, "E" },