diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31635d84..7f9bab73 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(logger) if (BUILD_ACQUIRE_DRIVER_ZARR) add_subdirectory(driver) endif () diff --git a/src/logger/CMakeLists.txt b/src/logger/CMakeLists.txt new file mode 100644 index 00000000..9d7c9998 --- /dev/null +++ b/src/logger/CMakeLists.txt @@ -0,0 +1,19 @@ +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +add_library(acquire-logger + logger.hh + logger.cpp +) + +target_include_directories(acquire-logger + PRIVATE + $ +) + +set_target_properties(acquire-logger PROPERTIES + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" +) + +install(TARGETS acquire-logger + LIBRARY DESTINATION lib +) \ No newline at end of file diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp new file mode 100644 index 00000000..77e92435 --- /dev/null +++ b/src/logger/logger.cpp @@ -0,0 +1,44 @@ +#include "logger.hh" + +#include +#include +#include + +LogLevel Logger::current_level_ = LogLevel_Info; +std::mutex Logger::log_mutex_{}; + +void +Logger::set_log_level(LogLevel level) +{ + current_level_ = level; +} + +LogLevel +Logger::get_log_level() +{ + return current_level_; +} + +std::string +Logger::get_timestamp_() +{ + + auto now = std::chrono::system_clock::now(); + auto time = std::chrono::system_clock::to_time_t(now); + auto ms = std::chrono::duration_cast( + now.time_since_epoch()) % + 1000; + + std::tm tm{}; +#if defined(_WIN32) + localtime_s(&tm, &time); +#else + localtime_r(&time, &tm); +#endif + + std::ostringstream ss; + ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << '.' << std::setfill('0') + << std::setw(3) << ms.count(); + + return ss.str(); +} \ No newline at end of file diff --git a/src/logger/logger.hh b/src/logger/logger.hh new file mode 100644 index 00000000..c5046990 --- /dev/null +++ b/src/logger/logger.hh @@ -0,0 +1,80 @@ +#include "logger.types.h" + +#include +#include +#include +#include + +class Logger +{ + public: + static void set_log_level(LogLevel level); + static LogLevel get_log_level(); + + template + static std::string log(LogLevel level, + const char* file, + int line, + const char* func, + Args&&... args) + { + namespace fs = std::filesystem; + + std::scoped_lock lock(log_mutex_); + + std::string prefix; + auto stream = &std::cout; + + switch (level) { + case LogLevel_Debug: + prefix = "[DEBUG] "; + break; + case LogLevel_Info: + prefix = "[INFO] "; + break; + case LogLevel_Warning: + prefix = "[WARNING] "; + break; + default: + prefix = "[ERROR] "; + stream = &std::cerr; + break; + } + + fs::path filepath(file); + std::string filename = filepath.filename().string(); + + std::ostringstream ss; + ss << get_timestamp_() << " " << prefix << filename << ":" << line + << " " << func << ": "; + + format_arg_(ss, std::forward(args)...); + + std::string message = ss.str(); + *stream << message << std::endl; + + return message; + } + + private: + static LogLevel current_level_; + static std::mutex log_mutex_; + + static void format_arg_(std::ostream& ss) {}; // base case + template + static void format_arg_(std::ostream& ss, T&& arg, Args&&... args) { + ss << std::forward(arg); + format_arg_(ss, std::forward(args)...); + } + + static std::string get_timestamp_(); +}; + +#define LOG_DEBUG(...) \ + Logger::log(LogLevel_Debug, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define LOG_INFO(...) \ + Logger::log(LogLevel_Info, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define LOG_WARNING(...) \ + Logger::log(LogLevel_Warning, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define LOG_ERROR(...) \ + Logger::log(LogLevel_Error, __FILE__, __LINE__, __func__, __VA_ARGS__) diff --git a/src/logger/logger.types.h b/src/logger/logger.types.h new file mode 100644 index 00000000..86d9c0b1 --- /dev/null +++ b/src/logger/logger.types.h @@ -0,0 +1,8 @@ +#pragma once + +typedef enum { + LogLevel_Debug, + LogLevel_Info, + LogLevel_Warning, + LogLevel_Error +} LogLevel;