diff --git a/libs/log/CMakeLists.txt b/libs/log/CMakeLists.txt new file mode 100644 index 00000000..2106bdbb --- /dev/null +++ b/libs/log/CMakeLists.txt @@ -0,0 +1,8 @@ +find_package(Threads) + +file(GLOB_RECURSE PSEMEK_LOG_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp") +file(GLOB_RECURSE PSEMEK_LOG_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp") + +add_library(log ${PSEMEK_LOG_HEADERS} ${PSEMEK_LOG_SOURCES}) +target_include_directories(log PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(log PUBLIC ${CMAKE_THREAD_LIBS_INIT}) diff --git a/libs/log/include/psemek/log/level.hpp b/libs/log/include/psemek/log/level.hpp new file mode 100644 index 00000000..d428a20d --- /dev/null +++ b/libs/log/include/psemek/log/level.hpp @@ -0,0 +1,27 @@ +#pragma once + +namespace psemek::log +{ + + enum class level + { + debug, + info, + warning, + error, + }; + + template + Stream & operator << (Stream & s, level l) + { + switch (l) + { + case level::debug: return s << "debug"; + case level::info: return s << "info"; + case level::warning: return s << "warning"; + case level::error: return s << "error"; + default: return s << "(unknown)"; + } + } + +} diff --git a/libs/log/include/psemek/log/log.hpp b/libs/log/include/psemek/log/log.hpp new file mode 100644 index 00000000..aca6ea1f --- /dev/null +++ b/libs/log/include/psemek/log/log.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include +#include + +namespace psemek::log +{ + + void set_max_thread_name_length(int length); + + void register_thread(std::string name); + void register_thread(std::thread::id id, std::string name); + + void put_message(level l, std::string const & message); + + struct log_stream + { + log_stream(level l) + : l_(l) + {} + + ~log_stream() + { + put_message(l_, os_.str()); + } + + template + log_stream & operator << (T const & x) + { + os_ << x; + return *this; + } + + private: + level l_; + std::ostringstream os_; + }; + + inline log_stream debug() { return {level::debug}; } + inline log_stream info() { return {level::info}; } + inline log_stream warning() { return {level::warning}; } + inline log_stream error() { return {level::error}; } + +} diff --git a/libs/log/source/log.cpp b/libs/log/source/log.cpp new file mode 100644 index 00000000..46211246 --- /dev/null +++ b/libs/log/source/log.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace psemek::log +{ + + static int max_thread_name_length = 5; + + void set_max_thread_name_length(int length) + { + max_thread_name_length = length; + } + + static std::unordered_map thread_names; + + void register_thread(std::string name) + { + register_thread(std::this_thread::get_id(), std::move(name)); + } + + void register_thread(std::thread::id id, std::string name) + { + auto it = thread_names.find(id); + if (it != thread_names.end()) + throw std::runtime_error("Thread \"" + name + "\" already registered!"); + + if (name.size() > max_thread_name_length) + throw std::runtime_error("Thread \"" + name + "\" name is too long"); + + thread_names[id] = name; + + put_message(level::info, "Thread \"" + name + "\" registered"); + } + + static ::tm safe_localtime(std::time_t const & time) + { + static std::mutex mutex; + std::lock_guard lock{mutex}; + return *std::localtime(&time); + } + + void put_message(level l, std::string const & message) + { + static std::string unknown_thread_name = "???"; + + using clock = std::chrono::system_clock; + + auto const time = clock::to_time_t(clock::now()); + auto const tm = safe_localtime(time); + + auto const millis = std::chrono::duration_cast(clock::now().time_since_epoch()).count() % 1000; + + auto const id = std::this_thread::get_id(); + auto const it = thread_names.find(id); + + auto const & thread_name = (it == thread_names.end()) ? unknown_thread_name : it->second; + + std::ostringstream os; + os + << '[' << std::put_time(&tm, "%Y %b %d %H:%M:%S.") << std::setw(3) << std::setfill('0') << millis << ']' + << '[' << std::setw(max_thread_name_length) << std::setfill(' ') << thread_name << ']' + << '[' << std::setw(7) << l << ']' + << ' ' << message; + + static std::mutex cout_mutex; + std::lock_guard lock{cout_mutex}; + std::cout << os.str() << std::endl; + } + +} diff --git a/todo.md b/todo.md index 79b94898..5e16cf8e 100644 --- a/todo.md +++ b/todo.md @@ -4,5 +4,4 @@ * Implement pixmap font rendering * Create a simple generic primive painter * Design resources system -* Design logging system * Design ui system