diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3885d2ef..1c3a88f4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,4 +21,4 @@ jobs:
- name: Run tests
working-directory: build
run: |
- ctest -C Debug --output-on-failure
+ ctest -C Debug --verbose
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7cf4c21b..20f94e96 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.0)
project(nodec LANGUAGES CXX)
add_library(${PROJECT_NAME} STATIC
- src/nodec/logging.cpp
+ # src/nodec/logging.cpp
src/nodec/unicode.cpp
)
diff --git a/docs/logging.drawio b/docs/logging.drawio
new file mode 100644
index 00000000..73d07eb5
--- /dev/null
+++ b/docs/logging.drawio
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/signals.drawio b/docs/signals.drawio
index 81d20124..61b2faf1 100644
--- a/docs/signals.drawio
+++ b/docs/signals.drawio
@@ -1 +1,187 @@
-7Vxdc5s4FP01nkkfmjFfBj/G+djtTjKTbbqzs4+KkY0mgDxCrpP8+pVAsgGBIbUVnEbpQ+EiZHHPubqHK2DkXCbPfxCwiu5wCOORPQ6fR87VyLat8WTK/uOWl8ISeFZhWBIUikY7wwN6hfJMYV2jEGaVhhTjmKJV1TjHaQrntGIDhOBNtdkCx9VfXYElVAwPcxCr1n9RSKPC6k6C0oE/IVpG4qcdZyxGngDZWhiyCIR4UzI51yPnkmBMi63k+RLG3HvSMcV5Ny1HtwMgMKV9Ttj8s3i8izb469+vf63Qf05guS9fba/o5ieI1+KSQ0gBijMxaPoiXcHGv+Kb6yS+ISBhm7NNhCh8WIE5t28YA5gtoknM9iy2ub1ivrNAcXyJY0zy3pzQg0Ho8kaU4CdYOhLYj85kwnsXDvfGvFvpZJvviUFDQuFzqzusrZMZPSFOICUvrMnzFqniFMHMyUTsb3Y4+1Nhi8oQS4SB4NZy2/fO+2xDAPAGMCwFi8uC1ginKhwblMQg5TAscEofxBHuahCjZcq258wVkHl1xh2FGKkvxAGKOVLzCMXhLXjBa35lGQXzJ7k3izBBr6xbsMWSAkJFfNqTSosHfiYzc2AIzFibe4mCVTPdgedKw1uQUWGY4zgGqww9bi8jAWSJ0hmmFCeiUY1EC4//Ex4o2Yu/JnJN8r86N49Ap6lfpZNjq3SyJg10spg39dBJdlzik8IidsFU8VOKC1qVnC1MklkxXNAGXiUoDHnPs4zNCShd/uA8u/pq7Sy3+YlX7s7yXbiCmwimgIKCARzuGDzC+B5nKI8A54oUbWcrjFKau8ubjbyr3EIYAVJ2HQDlgEHGrA3k7OqHbntAqpALiKV/OxF2NQHsdOMboxy3Al+ZwerT8ZHBdVRwHW3gVpmL0ggSpBN0t2dYB5ow95qCesYVFsqECDr7ojnMi/RB8DoNYSjQrMynvYJdG0JBP4S0zbteU1xyiB5jPH/65Oj47tDoqIq3QGedGnz4jVfPCU4fQAo+D8xxTIgaBXzSCti1qwrYs3oyydemjwIjgA/TQt5pC2DfCGANAng/6EML4KaY5ukbryABFJOzL2cXZJmdn5//7om8A6ehZfC0JY9/4yl5wWuGJqGfdELfVkRPJ6GrnDIJ/U1zxvS0E7qllsBNRj88o+9HfeiMbtktKV3Wsz5JPu9Aaeh8bqllre9gY1aoPko6n45/MZ1PbE2Msnvcv5l0vreW55x4Pm8qtpp8fvAS1X7Yh07onmviWi/APbWAvrhWZ+6dEPiWrEyx/oOJgcCzhxYDplh/6KThn7gY6FG9MWLg2LAPLQZc8xSa3rgeXAzYatFOVPqNEDh5IVBftfedvmlC22PQtqnyHzhjtGF+KkrANk+u6lACHbAPrgTanrz7ZHX+LpiGLvS7TWXZz/mIRQdUgz/MKt9hKkE1Axk0tZiPJMHqtRh/PLgEc3qswxsJtm/mcMbNmJ+KBHNsI8E0SLAO2IeWYM7nfjWlC53BlZeazj/XuykdAA2vt3qsbMI0vOBfHmB7OWjMISHIotyf3IXs+A3iP5t7l+0JaWTZo8rb81UUip+BofK5gprKYEPBazKHey5CLt4xWbaEv4JGk/sJjAFFP6uja/K/6O6eT9Q7DRQEtbfx5XqU7KK4KnHWDkWlI2vsdvRUXLbSU86H7UUeQJEejzyUKMLuqdIq7jVG5OpZtg4RSHAa/ohQWhPWlisNZXIxukh1z9JghJc4BfH1zto1HzwKBa2PjNuvenSRsa0KLnCXH0+Qr98fhZUThZW1iaUvKwOFlLWOWkjJcAcvpWZC3rQO2Ksv6/qTGseLHo/KeK+hXnDtjJjvgos5gYBCuTtTI4Fx6JZruWoMSFpKWdfOS34vWNKFqv5rTUai6iROHm2/09KdpFpevBDdj88DT7j9uGy0qxSXr//LDvBikUE9U1qPJbz3mNKsjzGleT1ntI6ygJ4Zrbbcs91/64zm16fGeke60+whk47C1RpBK3rtPSRZX8q0PCKuQZD9NkRxG54r+rhEmfYkStsDhIYpe5jSY93ZKPc3U3GINOfXvsbleb/IyanT0dGRhLtfv0Fwx3vHVRf6tfaahH5TecoEyP45+IA6i7m1LUdifcD+/gjpaK8pQnqsm5kIqX/w0gTIMQLEsvbnhO4IeZccYisRUqjwkBkXhPFNkwjvDocBiP2OMn3qVdF2/XeW6W/m89huHnF76X//CW8lNNvdfZ25aL77yLVz/T8=
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/include/nodec/concurrent/thread_pool_executor.hpp b/include/nodec/concurrent/thread_pool_executor.hpp
index 42e81b18..582f0387 100644
--- a/include/nodec/concurrent/thread_pool_executor.hpp
+++ b/include/nodec/concurrent/thread_pool_executor.hpp
@@ -1,8 +1,6 @@
#ifndef NODEC__CONCURRENT__THREAD_POOL_EXECUTOR_HPP_
#define NODEC__CONCURRENT__THREAD_POOL_EXECUTOR_HPP_
-#include
-
#include
#include
#include
diff --git a/include/nodec/logging.hpp b/include/nodec/logging.hpp
deleted file mode 100644
index f71df32b..00000000
--- a/include/nodec/logging.hpp
+++ /dev/null
@@ -1,159 +0,0 @@
-#ifndef NODEC__LOGGING_HPP_
-#define NODEC__LOGGING_HPP_
-
-#include
-
-#include
-#include
-#include
-
-namespace nodec {
-namespace logging {
-
-enum class Level {
- Unset = 0, //! The unset log level
- Debug = 10, //! The debug log level
- Info = 20, //! The info log level
- Warn = 30, //! The warn log level
- Error = 40, //! The error log level
- Fatal = 50 //! The fatal log level
-};
-
-struct LogRecord {
- LogRecord(Level level, const std::string &message, const char *file, size_t line);
- Level level;
- std::string message;
- const char *file;
- size_t line;
-};
-
-using RecordHandlers = signals::Signal;
-
-extern std::function formatter;
-
-std::string default_formatter(const LogRecord &record) noexcept;
-
-/**
- * @brief Record handlers can be connected through this interface.
- *
- * Example:
- * // connect stdout handler.
- * record_handlers().connect(logging::record_to_stdout_handler);
- *
- * @todo Support thread-safe.
- */
-RecordHandlers::SignalInterface record_handlers();
-
-/**
- * @brief Output the log record to stdout.
- */
-void record_to_stdout_handler(const LogRecord &record) noexcept;
-
-/**
- * @brief Sets the threshold for this logger to level.
- * Logging messages which are less severe than level will be ignored;
- * logging messages which have severity level or higher will be emitted
- * by whichever handler or handlers service this logger
- */
-void set_level(Level level);
-
-/**
- * @detail
- * DEBUG: Detailed information, typically of interest only when diagnosing problems.
- */
-void debug(const std::string &message, const char *file, size_t line);
-
-/**
- * @detail
- * INFO: Confirmation that things are working as expected.
- */
-void info(const std::string &message, const char *file, size_t line);
-
-/**
- * @detail
- * WARNING: An indication that something unexpected happened, or indicative of some problem in the near future (e.g. 'disk space low').
- * The software is still working as expected.
- */
-void warn(const std::string &message, const char *file, size_t line);
-
-/**
- * @detail
- * ERROR: Due to a more serious problem, the software has not been able to perform some function.
- */
-void error(const std::string &message, const char *file, size_t line);
-
-/**
- * @detail
- * FATAL: A serious error, indicating that the program itself may be unable to continue running.
- */
-void fatal(const std::string &message, const char *file, size_t line);
-
-/**
- * @brief Logs a message with level.
- */
-void log(Level level, const std::string &message, const char *file, size_t line);
-
-namespace details {
-class LogStreamBufferGeneric;
-}
-
-class LogStream : public std::ostream {
-public:
- LogStream(Level level, const char *file, size_t line);
- ~LogStream();
-
-private:
- details::LogStreamBufferGeneric *buffer;
-};
-
-class DebugStream : public LogStream {
-public:
- DebugStream(const char *file, size_t line);
-};
-
-class InfoStream : public LogStream {
-public:
- InfoStream(const char *file, size_t line);
-};
-
-class WarnStream : public LogStream {
-public:
- WarnStream(const char *file, size_t line);
-};
-
-class ErrorStream : public LogStream {
-public:
- ErrorStream(const char *file, size_t line);
-};
-
-class FatalStream : public LogStream {
-public:
- FatalStream(const char *file, size_t line);
-};
-
-inline std::ostream &operator<<(std::ostream &stream, const nodec::logging::Level &level) {
- switch (level) {
- case nodec::logging::Level::Unset:
- return stream << "Unset";
- case nodec::logging::Level::Debug:
- return stream << "Debug";
- case nodec::logging::Level::Info:
- return stream << "Info";
- case nodec::logging::Level::Warn:
- return stream << "Warn";
- case nodec::logging::Level::Error:
- return stream << "Error";
- case nodec::logging::Level::Fatal:
- return stream << "Fatal";
- }
- return stream << "Unknown";
-}
-
-inline std::ostream &operator<<(std::ostream &stream, const nodec::logging::LogRecord &record) {
- return stream << nodec::logging::formatter(record);
-}
-
-} // namespace logging
-} // namespace nodec
-
-#endif
\ No newline at end of file
diff --git a/include/nodec/logging/formatters/simple_formatter.hpp b/include/nodec/logging/formatters/simple_formatter.hpp
new file mode 100644
index 00000000..1497029b
--- /dev/null
+++ b/include/nodec/logging/formatters/simple_formatter.hpp
@@ -0,0 +1,54 @@
+#ifndef NODEC__LOGGING__SIMPLE_FORMATTER_HPP_
+#define NODEC__LOGGING__SIMPLE_FORMATTER_HPP_
+
+#include
+
+#include "../log_record.hpp"
+
+namespace nodec {
+namespace logging {
+namespace formatters {
+
+struct SimpleFormatter {
+ std::string operator()(const LogRecord &record) {
+ std::ostringstream oss;
+
+ switch (record.level) {
+ case nodec::logging::Level::Unset:
+ oss << "[UNSET]";
+ break;
+ case nodec::logging::Level::Debug:
+ oss << "[DEBUG]";
+ break;
+ case nodec::logging::Level::Info:
+ oss << "[INFO] ";
+ break;
+ case nodec::logging::Level::Warn:
+ oss << "[WARN] ";
+ break;
+ case nodec::logging::Level::Error:
+ oss << "[ERROR]";
+ break;
+ case nodec::logging::Level::Fatal:
+ oss << "[FATAL]";
+ break;
+ default:
+ oss << "[???] ";
+ break;
+ }
+
+ if (!record.name.empty()) {
+ oss << " [" << record.name << "]";
+ }
+
+ oss << " - " << record.message << "\n";
+ oss << "(" << record.file << " line " << record.line << ")\n";
+
+ return oss.str();
+ }
+};
+
+} // namespace formatters
+} // namespace logging
+} // namespace nodec
+#endif
\ No newline at end of file
diff --git a/include/nodec/logging/handlers/stdout_handler.hpp b/include/nodec/logging/handlers/stdout_handler.hpp
new file mode 100644
index 00000000..d1582c91
--- /dev/null
+++ b/include/nodec/logging/handlers/stdout_handler.hpp
@@ -0,0 +1,38 @@
+#ifndef NODEC__LOGGING__HANDLERS__STDOUT_HANDLER_HPP_
+#define NODEC__LOGGING__HANDLERS__STDOUT_HANDLER_HPP_
+
+#include
+#include
+
+#include "../log_record.hpp"
+
+namespace nodec {
+namespace logging {
+namespace handlers {
+
+struct ConsoleMutex {
+ static std::mutex &get() {
+ static std::mutex mutex;
+ return mutex;
+ }
+};
+
+template
+class StdoutHandler {
+private:
+public:
+ StdoutHandler(Formatter formatter = Formatter{})
+ : formatter_(formatter) {}
+
+ void operator()(const LogRecord &record) {
+ std::lock_guard lock(ConsoleMutex::get());
+ std::cout << formatter_(record);
+ }
+
+private:
+ Formatter formatter_;
+};
+} // namespace handlers
+} // namespace logging
+} // namespace nodec
+#endif
\ No newline at end of file
diff --git a/include/nodec/logging/level.hpp b/include/nodec/logging/level.hpp
new file mode 100644
index 00000000..69ea4570
--- /dev/null
+++ b/include/nodec/logging/level.hpp
@@ -0,0 +1,38 @@
+#ifndef NODEC__LOGGING__LEVEL_HPP_
+#define NODEC__LOGGING__LEVEL_HPP_
+
+#include
+
+namespace nodec {
+namespace logging {
+
+enum class Level {
+ Unset = 0, //! The unset log level
+ Debug = 10, //! The debug log level
+ Info = 20, //! The info log level
+ Warn = 30, //! The warn log level
+ Error = 40, //! The error log level
+ Fatal = 50 //! The fatal log level
+};
+
+inline std::ostream &operator<<(std::ostream &stream, const nodec::logging::Level &level) {
+ switch (level) {
+ case nodec::logging::Level::Unset:
+ return stream << "Unset";
+ case nodec::logging::Level::Debug:
+ return stream << "Debug";
+ case nodec::logging::Level::Info:
+ return stream << "Info";
+ case nodec::logging::Level::Warn:
+ return stream << "Warn";
+ case nodec::logging::Level::Error:
+ return stream << "Error";
+ case nodec::logging::Level::Fatal:
+ return stream << "Fatal";
+ }
+ return stream << "Unknown";
+}
+
+} // namespace logging
+} // namespace nodec
+#endif
\ No newline at end of file
diff --git a/include/nodec/logging/log_record.hpp b/include/nodec/logging/log_record.hpp
new file mode 100644
index 00000000..1907bb82
--- /dev/null
+++ b/include/nodec/logging/log_record.hpp
@@ -0,0 +1,25 @@
+#ifndef NODEC__LOGGING__LOG_RECORD_HPP_
+#define NODEC__LOGGING__LOG_RECORD_HPP_
+
+#include
+#include
+
+#include "level.hpp"
+
+namespace nodec {
+namespace logging {
+
+struct LogRecord {
+ LogRecord(const std::string &name, Level level, const std::string &message, const char *file, std::size_t line)
+ : name(name), level(level), message(message), file(file), line(line) {}
+
+ const std::string &name;
+ Level level;
+ const std::string &message;
+ const char *file;
+ std::size_t line;
+};
+
+} // namespace logging
+} // namespace nodec
+#endif
\ No newline at end of file
diff --git a/include/nodec/logging/logger.hpp b/include/nodec/logging/logger.hpp
new file mode 100644
index 00000000..c9e91f84
--- /dev/null
+++ b/include/nodec/logging/logger.hpp
@@ -0,0 +1,205 @@
+#ifndef NODEC__LOGGING__LOGGER_HPP_
+#define NODEC__LOGGING__LOGGER_HPP_
+
+#include
+#include
+#include
+#include
+#include
+
+#include "../macros.hpp"
+#include "../optional.hpp"
+#include "../signals/signal.hpp"
+#include "log_record.hpp"
+
+namespace nodec {
+namespace logging {
+
+class HandlerConnection {
+public:
+ HandlerConnection(signals::Connection &&connection, std::mutex &handlers_mutex)
+ : conn_(std::move(connection)), handlers_mutex_(&handlers_mutex) {}
+
+ ~HandlerConnection() {
+ std::lock_guard lock(*handlers_mutex_);
+ conn_.reset();
+ }
+
+ HandlerConnection(HandlerConnection &&other) noexcept
+ : conn_(std::move(other.conn_)), handlers_mutex_(other.handlers_mutex_) {
+ other.handlers_mutex_ = nullptr;
+ }
+
+ /**
+ * @brief Blocks the connection.
+ * During blocking, the handler belongs to the connection won't be emitted.
+ *
+ * @warning
+ * Use this function only during handler is emitted.
+ */
+ void block() {
+ conn_->block();
+ }
+
+ void unblock() {
+ conn_->unblock();
+ }
+
+private:
+ optional conn_;
+ std::mutex *handlers_mutex_;
+ NODEC_DISABLE_COPY(HandlerConnection)
+};
+
+class Logger {
+private:
+ void handle(const LogRecord &record) {
+ if (record.level < level_) return;
+ {
+ std::lock_guard lock(handlers_mutex_);
+ handlers_(record);
+ }
+ if (parent_) {
+ parent_->handle(record);
+ }
+ }
+
+ class LogStream {
+ public:
+ LogStream(Logger &logger, Level level, const char *file, std::size_t line)
+ : logger_(logger), level_(level), file_(file), line_(line) {}
+
+ ~LogStream() {
+ logger_.log(level_, stream_.str(), file_, line_);
+ }
+
+ template
+ LogStream &operator<<(const T &value) {
+ stream_ << value;
+ return *this;
+ }
+
+ private:
+ Logger &logger_;
+ Level level_;
+ const char *file_;
+ std::size_t line_;
+ std::ostringstream stream_;
+ };
+
+public:
+ Logger(const std::string &name, std::shared_ptr parent)
+ : name_(name), parent_(parent) {}
+
+ /**
+ * @brief Sets the threshold for this logger to level.
+ * Logging messages which are less severe than level will be ignored;
+ * logging messages which have severity level or higher will be emitted
+ * by whichever handler or handlers service this logger
+ */
+ void set_level(Level level) noexcept {
+ level_ = level;
+ }
+
+ template
+ HandlerConnection add_handler(Handler &&handler) {
+ std::lock_guard lock(handlers_mutex_);
+ signals::Connection conn_raw = handlers_.signal_interface().connect(std::move(handler));
+ return HandlerConnection{std::move(conn_raw), handlers_mutex_};
+ }
+
+ template
+ void add_handler(std::shared_ptr handler) {
+ if (!handler) return;
+ std::lock_guard lock(handlers_mutex_);
+ handlers_.signal_interface().connect([handler](const LogRecord &record) {
+ (*handler)(record);
+ });
+ }
+
+ /**
+ * @detail
+ * DEBUG: Detailed information, typically of interest only when diagnosing problems.
+ */
+ void debug(const std::string &message, const char *file, std::size_t line) {
+ handle(LogRecord{name_, Level::Debug, message, file, line});
+ }
+
+ /**
+ * @detail
+ * INFO: Confirmation that things are working as expected.
+ */
+ void info(const std::string &message, const char *file, std::size_t line) {
+ handle(LogRecord{name_, Level::Info, message, file, line});
+ }
+
+ /**
+ * @detail
+ * WARNING: An indication that something unexpected happened, or indicative of some problem in the near future (e.g. 'disk space low').
+ * The software is still working as expected.
+ */
+ void warn(const std::string &message, const char *file, std::size_t line) {
+ handle(LogRecord{name_, Level::Warn, message, file, line});
+ }
+
+ /**
+ * @detail
+ * ERROR: Due to a more serious problem, the software has not been able to perform some function.
+ */
+ void error(const std::string &message, const char *file, std::size_t line) {
+ handle(LogRecord{name_, Level::Error, message, file, line});
+ }
+
+ /**
+ * @detail
+ * FATAL: A serious error, indicating that the program itself may be unable to continue running.
+ */
+ void fatal(const std::string &message, const char *file, std::size_t line) {
+ handle(LogRecord{name_, Level::Fatal, message, file, line});
+ }
+
+ /**
+ * @brief Logs a message with level.
+ */
+ void log(Level level, const std::string &message, const char *file, std::size_t line) {
+ handle(LogRecord{name_, level, message, file, line});
+ }
+
+ LogStream debug(const char *file, std::size_t line) {
+ return {*this, Level::Debug, file, line};
+ }
+
+ LogStream info(const char *file, std::size_t line) {
+ return {*this, Level::Info, file, line};
+ }
+
+ LogStream warn(const char *file, std::size_t line) {
+ return {*this, Level::Warn, file, line};
+ }
+
+ LogStream error(const char *file, std::size_t line) {
+ return {*this, Level::Error, file, line};
+ }
+
+ LogStream fatal(const char *file, std::size_t line) {
+ return {*this, Level::Fatal, file, line};
+ }
+
+ LogStream log(Level level, const char *file, std::size_t line) {
+ return {*this, level, file, line};
+ }
+
+private:
+ const std::string name_;
+ std::atomic level_{Level::Unset};
+ std::shared_ptr parent_;
+
+ std::mutex handlers_mutex_;
+ signals::Signal handlers_;
+
+ NODEC_DISABLE_COPY(Logger)
+};
+
+} // namespace logging
+} // namespace nodec
+#endif
\ No newline at end of file
diff --git a/include/nodec/logging/logger_repository.hpp b/include/nodec/logging/logger_repository.hpp
new file mode 100644
index 00000000..bdd820c0
--- /dev/null
+++ b/include/nodec/logging/logger_repository.hpp
@@ -0,0 +1,60 @@
+#ifndef NODEC__LOGGING__LOGGER_REPOSITORY_HPP_
+#define NODEC__LOGGING__LOGGER_REPOSITORY_HPP_
+
+#include
+#include
+#include
+#include
+
+#include "logger.hpp"
+
+namespace nodec {
+namespace logging {
+
+class LoggerRepository {
+public:
+ LoggerRepository()
+ : root_logger_(get_logger("")) {
+ }
+
+ std::shared_ptr get_logger(const std::string &name) {
+ std::lock_guard lock(loggers_mutex_);
+ auto &logger = loggers_[name];
+ if (logger) return logger;
+
+ if (name.empty()) {
+ logger = std::make_shared("", nullptr);
+ return logger;
+ }
+
+ std::string parent_name;
+ {
+ auto index = name.find_last_of('.');
+ if (index != std::string::npos) {
+ parent_name = name.substr(0, index);
+ }
+ }
+ logger = std::make_shared(name, get_logger(parent_name));
+ return logger;
+ }
+
+ std::shared_ptr root_logger() const noexcept {
+ return root_logger_;
+ }
+
+private:
+ std::recursive_mutex loggers_mutex_;
+ std::unordered_map> loggers_;
+ std::shared_ptr root_logger_;
+};
+
+class LoggerRepositoryLocater {
+public:
+ static LoggerRepository &get() {
+ static LoggerRepository instance;
+ return instance;
+ }
+};
+} // namespace logging
+} // namespace nodec
+#endif
\ No newline at end of file
diff --git a/include/nodec/logging/logging.hpp b/include/nodec/logging/logging.hpp
new file mode 100644
index 00000000..8ecc1756
--- /dev/null
+++ b/include/nodec/logging/logging.hpp
@@ -0,0 +1,87 @@
+#ifndef NODEC__LOGGING__LOGGING_HPP_
+#define NODEC__LOGGING__LOGGING_HPP_
+
+#include "logger.hpp"
+#include "logger_repository.hpp"
+
+namespace nodec {
+namespace logging {
+
+class LogStream {
+public:
+ LogStream(std::shared_ptr logger, Level level, const char *file, std::size_t line)
+ : logger_(logger), level_(level), file_(file), line_(line) {}
+
+ ~LogStream() {
+ logger_->log(level_, stream_.str(), file_, line_);
+ }
+
+ template
+ LogStream &operator<<(const T &value) {
+ stream_ << value;
+ return *this;
+ }
+
+private:
+ std::shared_ptr logger_;
+ Level level_;
+ const char *file_;
+ std::size_t line_;
+ std::ostringstream stream_;
+};
+
+inline std::shared_ptr get_logger(const std::string &name = "") {
+ return LoggerRepositoryLocater::get().get_logger(name);
+}
+
+inline void debug(const std::string &message, const char *file, std::size_t line) {
+ LoggerRepositoryLocater::get().root_logger()->debug(message, file, line);
+}
+
+inline void info(const std::string &message, const char *file, std::size_t line) {
+ LoggerRepositoryLocater::get().root_logger()->info(message, file, line);
+}
+
+inline void warn(const std::string &message, const char *file, std::size_t line) {
+ LoggerRepositoryLocater::get().root_logger()->warn(message, file, line);
+}
+
+inline void error(const std::string &message, const char *file, std::size_t line) {
+ LoggerRepositoryLocater::get().root_logger()->error(message, file, line);
+}
+
+inline void fatal(const std::string &message, const char *file, std::size_t line) {
+ LoggerRepositoryLocater::get().root_logger()->fatal(message, file, line);
+}
+
+inline void log(Level level, const std::string &message, const char *file, std::size_t line) {
+ LoggerRepositoryLocater::get().root_logger()->log(level, message, file, line);
+}
+
+inline LogStream debug(const char *file, std::size_t line) {
+ return {LoggerRepositoryLocater::get().root_logger(), Level::Debug, file, line};
+}
+
+inline LogStream info(const char *file, std::size_t line) {
+ return {LoggerRepositoryLocater::get().root_logger(), Level::Info, file, line};
+}
+
+inline LogStream warn(const char *file, std::size_t line) {
+ return {LoggerRepositoryLocater::get().root_logger(), Level::Warn, file, line};
+}
+
+inline LogStream error(const char *file, std::size_t line) {
+ return {LoggerRepositoryLocater::get().root_logger(), Level::Error, file, line};
+}
+
+inline LogStream fatal(const char *file, std::size_t line) {
+ return {LoggerRepositoryLocater::get().root_logger(), Level::Fatal, file, line};
+}
+
+inline LogStream log(Level level, const char *file, std::size_t line) {
+ return {LoggerRepositoryLocater::get().root_logger(), level, file, line};
+}
+} // namespace logging
+} // namespace nodec
+
+#endif
\ No newline at end of file
diff --git a/include/nodec/riff.hpp b/include/nodec/riff.hpp
index 02b50904..a4610b1a 100644
--- a/include/nodec/riff.hpp
+++ b/include/nodec/riff.hpp
@@ -1,13 +1,12 @@
#ifndef NODEC__RIFF_HPP_
#define NODEC__RIFF_HPP_
-#include
-#include
-
#include
#include
#include
+#include
+
namespace nodec {
namespace riff {
diff --git a/src/nodec/logging.cpp b/src/nodec/logging.cpp
deleted file mode 100644
index add78073..00000000
--- a/src/nodec/logging.cpp
+++ /dev/null
@@ -1,170 +0,0 @@
-#include
-
-#include
-#include
-#include
-
-namespace nodec {
-namespace logging {
-
-namespace {
-Level current_level_ = Level::Debug;
-RecordHandlers record_handlers_;
-} // namespace
-
-std::function formatter = default_formatter;
-
-LogRecord::LogRecord(Level level, const std::string &message, const char *file, size_t line)
- : level(level),
- message(message),
- file(file),
- line(line) {
-}
-
-void set_level(Level level) {
- current_level_ = level;
-}
-
-std::string default_formatter(const LogRecord &record) noexcept {
- std::ostringstream oss;
- oss << "[" << record.file << " line " << record.line << "]\n";
- switch (record.level) {
- case nodec::logging::Level::Unset:
- oss << "UNSET : ";
- break;
- case nodec::logging::Level::Debug:
- oss << "DEBUG : ";
- break;
- case nodec::logging::Level::Info:
- oss << "INFO : ";
- break;
- case nodec::logging::Level::Warn:
- oss << "WARNING: ";
- break;
- case nodec::logging::Level::Error:
- oss << "ERROR : ";
- break;
- case nodec::logging::Level::Fatal:
- oss << "FATAL : ";
- break;
- default:
- oss << "UNKOWN : ";
- break;
- }
-
- oss << record.message << "\n";
-
- return oss.str();
-}
-
-void record_to_stdout_handler(const LogRecord &record) noexcept {
- std::cout << record << std::endl;
-}
-
-RecordHandlers::SignalInterface record_handlers() {
- return record_handlers_.signal_interface();
-}
-
-namespace {
-void log_generic(const LogRecord &record) {
- // Wait until one record handled.
- static std::mutex io_lock_mtx_;
- std::lock_guard lock(io_lock_mtx_);
-
- if (record.level < current_level_) {
- // ignore this log
- return;
- }
-
- // Handler objects are dispatching the appropriate log messages (based on the log messages' severity)
- // to the handler's specified destination.
- record_handlers_(record);
-}
-} // namespace
-
-void debug(const std::string &message, const char *file, size_t line) {
- log_generic(LogRecord{Level::Debug, message, file, line});
-}
-
-void info(const std::string &message, const char *file, size_t line) {
- log_generic(LogRecord{Level::Info, message, file, line});
-}
-
-void warn(const std::string &message, const char *file, size_t line) {
- log_generic(LogRecord{Level::Warn, message, file, line});
-}
-
-void error(const std::string &message, const char *file, size_t line) {
- log_generic(LogRecord{Level::Error, message, file, line});
-}
-
-void fatal(const std::string &message, const char *file, size_t line) {
- log_generic(LogRecord{Level::Fatal, message, file, line});
-}
-
-void log(Level level, const std::string &message, const char *file, size_t line) {
- log_generic(LogRecord{level, message, file, line});
-}
-
-namespace details {
-class LogStreamBufferGeneric : public std::streambuf {
- using Base = std::streambuf;
-
-public:
- LogStreamBufferGeneric(const Level level, const char *file, size_t line)
- : level(level),
- file(file),
- line(line) {
- }
-
-public:
- int overflow(int ch) override {
- message += ch;
- return ch;
- }
- int sync() override {
- if (!message.empty()) {
- log_generic(LogRecord{level, message, file, line});
- message.clear();
- }
- return 0; // not -1: means it ok.
- }
-
-private:
- std::string message;
- Level level;
- const char *file;
- size_t line;
-};
-} // namespace details
-
-LogStream::LogStream(Level level, const char *file, size_t line)
- : std::ostream(buffer = new details::LogStreamBufferGeneric(level, file, line)) {}
-
-LogStream::~LogStream() {
- buffer->sync();
- delete buffer;
-}
-
-DebugStream::DebugStream(const char *file, size_t line)
- : LogStream(Level::Debug, file, line) {
-}
-
-InfoStream::InfoStream(const char *file, size_t line)
- : LogStream(Level::Info, file, line) {
-}
-
-WarnStream::WarnStream(const char *file, size_t line)
- : LogStream(Level::Warn, file, line) {
-}
-
-ErrorStream::ErrorStream(const char *file, size_t line)
- : LogStream(Level::Error, file, line) {
-}
-
-FatalStream::FatalStream(const char *file, size_t line)
- : LogStream(Level::Fatal, file, line) {
-}
-
-} // namespace logging
-} // namespace nodec
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2314f5c4..84eb9901 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -8,11 +8,14 @@ function(add_basic_test TARGET TEST_SOURCES)
add_test(NAME ${TARGET} COMMAND ${TARGET})
endfunction(add_basic_test)
+add_basic_test("array_view" array_view/array_view.cpp)
add_basic_test("containers__sparse_table" containers/sparse_table.cpp)
add_basic_test("entitites__registry" entities/registry.cpp)
add_basic_test("entitites__storage" entities/storage.cpp)
add_basic_test("flags" flags/flags.cpp)
add_basic_test("formatter" formatter/formatter.cpp)
+add_basic_test("logging_bench" logging/bench.cpp)
+add_basic_test("logging__logging" logging/logging.cpp)
add_basic_test("math" math/math.cpp)
add_basic_test("math_gfx" math/gfx.cpp)
add_basic_test("matrix4x4" matrix4x4/matrix4x4.cpp)
@@ -26,4 +29,3 @@ add_basic_test("stopwatch" stopwatch/stopwatch.cpp)
add_basic_test("type_info" type_info/type_info.cpp)
add_basic_test("utility" utility/utility.cpp)
add_basic_test("vector" vector/vector.cpp)
-add_basic_test("array_view" array_view/array_view.cpp)
diff --git a/tests/logging/bench.cpp b/tests/logging/bench.cpp
new file mode 100644
index 00000000..4e81a1b5
--- /dev/null
+++ b/tests/logging/bench.cpp
@@ -0,0 +1,72 @@
+#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+TEST_CASE("Single thread, 100,000 iterations") {
+ int iters = 100000;
+
+ using namespace nodec::logging;
+ auto logger = get_logger();
+
+ // auto handler = std::make_shared>();
+ // auto conn = logger->add_handler([&](const LogRecord &record) { (*handler)(record); });
+
+ using std::chrono::duration;
+ using std::chrono::duration_cast;
+ using std::chrono::high_resolution_clock;
+
+ auto start = high_resolution_clock::now();
+ for (auto i = 0; i < iters; ++i) {
+ logger->info(__FILE__, __LINE__) << "Hello logger: msg number " << i;
+ }
+
+ auto delta = high_resolution_clock::now() - start;
+ auto delta_d = duration_cast>(delta).count();
+
+ MESSAGE("Elapsed: ", delta_d, " secs ", std::size_t(iters / delta_d), "/sec");
+
+ // NOTE: '<<' operator is very slow.
+ // About 50% of total cpu consumption is occupied...
+ // Should use std::format() instead if c++17 is available.
+}
+
+TEST_CASE("10 threads, competing over the same logger object, 100,000 iterations") {
+ int iters = 100000;
+ int thread_count = 10;
+
+ using namespace nodec::logging;
+ auto logger = get_logger();
+
+ // auto handler = std::make_shared>();
+ // auto conn = logger->add_handler([&](const LogRecord &record) { (*handler)(record); });
+
+ using std::chrono::duration;
+ using std::chrono::duration_cast;
+ using std::chrono::high_resolution_clock;
+
+ std::vector threads;
+ threads.reserve(thread_count);
+
+ auto start = high_resolution_clock::now();
+ for (std::size_t i = 0; i < thread_count; ++i) {
+ threads.emplace_back([&, i]() {
+ for (auto j = 0; j < iters; ++j) {
+ logger->info(__FILE__, __LINE__) << "Hello logger: msg number " << i << ", " << j;
+ }
+ });
+ }
+ for (auto &t : threads) {
+ t.join();
+ };
+
+ auto delta = high_resolution_clock::now() - start;
+ auto delta_d = duration_cast>(delta).count();
+
+ MESSAGE("Elapsed: ", delta_d, " secs ", std::size_t(iters / delta_d), "/sec");
+}
\ No newline at end of file
diff --git a/tests/logging/logging.cpp b/tests/logging/logging.cpp
new file mode 100644
index 00000000..b6fc41e4
--- /dev/null
+++ b/tests/logging/logging.cpp
@@ -0,0 +1,58 @@
+#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+#include
+
+#include
+
+TEST_CASE("Testing add_handler") {
+ using namespace nodec::logging;
+
+ auto logger = get_logger();
+ int count = 0;
+
+ // Check the handler is called.
+ {
+ auto conn = logger->add_handler([&](const LogRecord &record) {
+ count++;
+ });
+
+ logger->debug("hello", __FILE__, __LINE__);
+ CHECK(count == 1);
+ }
+
+ // Check the handler is disconnected.
+ {
+ count = 0;
+ logger->debug("hello", __FILE__, __LINE__);
+ CHECK(count == 0);
+ }
+
+ // Check the parent handler is called.
+ {
+ auto conn1 = logger->add_handler([&](const LogRecord &record) {
+ count++;
+ });
+
+ auto sub_logger = get_logger("sub");
+ auto conn2 = sub_logger->add_handler([&](const LogRecord &record) {
+ count++;
+ });
+ sub_logger->debug("hello", __FILE__, __LINE__);
+
+ CHECK(count == 2);
+ }
+}
+
+TEST_CASE("Testing LogStream") {
+ using namespace nodec::logging;
+
+ auto logger = get_logger();
+ {
+ std::string result;
+ auto conn = logger->add_handler([&](const LogRecord &record) {
+ result = record.message;
+ });
+ logger->debug(__FILE__, __LINE__) << "Hello" << 100;
+
+ CHECK(result == "Hello100");
+ }
+}
diff --git a/tests/logging/test_basic/main.cpp b/tests/logging/test_basic/main.cpp
deleted file mode 100644
index c6c5d2ad..00000000
--- a/tests/logging/test_basic/main.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-#include "sub.hpp"
-
-#include
-
-#include
-#include
-
-#include
-#include
-#include
-
-
-class Logger
-{
-public:
- Logger()
- {
- log_file.open("output.log", std::ios::binary);
-
- }
-
- void record_to_file_handler(const nodec::logging::LogRecord& record) noexcept
- {
- log_file << record << "\n";
- }
-
-public:
- std::ofstream log_file;
- nodec::logging::MemberRecordHandler::SharedPtr handler;
-};
-
-int main()
-{
- auto logger = std::make_shared();
- logger->handler = nodec::logging::MemberRecordHandler::make_shared(logger, &Logger::record_to_file_handler);
- nodec::logging::record_handlers += logger->handler;
-
- std::cout << "--- 1 ---" << std::endl;
-
- std::string message = "test message";
-
- //nodec::logging::formatter(message);
- std::cout << message << std::endl;
-
- nodec::logging::Level level;
- level = nodec::logging::Level::Debug;
-
- std::cout << level << std::endl;
-
-
- std::cout << "--- 2 ---" << std::endl;
- nodec::logging::LogRecord record(nodec::logging::Level::Error, "are you ok?", __FILE__, __LINE__);
- std::cout << record.message << std::endl;
- nodec::logging::LogRecord record_copy = record;
- std::cout << record_copy.message << std::endl;
-
- std::cout << nodec::logging::formatter(record) << std::endl;
-
-
- std::cout << "--- 3 ---" << std::endl;
- {
- auto record_handler_ptr = nodec::logging::StaticRecordHandler::make_shared(nodec::logging::record_to_stdout_handler);
- nodec::logging::record_handlers += record_handler_ptr;
- }
- nodec::logging::debug("debug message.", __FILE__, __LINE__);
- nodec::logging::info("info message.", __FILE__, __LINE__);
- nodec::logging::warn("warn message.", __FILE__, __LINE__);
- nodec::logging::error("error message.", __FILE__, __LINE__);
- nodec::logging::fatal("fatal message.", __FILE__, __LINE__);
-
-
- std::cout << "--- 4 ---" << std::endl;
-
- nodec::logging::set_level(nodec::logging::Level::Error);
- nodec::logging::debug("debug message.", __FILE__, __LINE__);
- nodec::logging::info("info message.", __FILE__, __LINE__);
- nodec::logging::warn("warn message.", __FILE__, __LINE__);
- nodec::logging::error("error message.", __FILE__, __LINE__);
- nodec::logging::fatal("fatal message.", __FILE__, __LINE__);
-
-
- std::cout << "--- 5 ---" << std::endl;
- nodec::logging::set_level(nodec::logging::Level::Info);
- sub_func();
- //nodec::logging::formatter = ;
-
- std::cout << "--- 6 ---" << std::endl;
- nodec::logging::DebugStream(__FILE__, __LINE__) << "Debug" << std::endl;
- nodec::logging::FatalStream(__FILE__, __LINE__) << "Fatal\n"
- << "OKOK\n"
- << "No problem!" << std::flush;;
-
- std::cout << "--- 7 ---" << std::endl;
- {
- nodec::logging::ErrorStream error_stream(__FILE__, __LINE__);
- error_stream << "ERROR";
- error_stream << nodec::logging::Level::Debug;
- }
- nodec::logging::InfoStream(__FILE__, __LINE__) << "info" << std::flush << std::flush;
-
- //std::ostringstream() << "A" << nodec::logging::Level::Debug;
-
- //oss << "TEST";
- //oss << nodec::logging::Level::Debug << "A";
- //nodec::logging::InfoStream(__FILE__, __LINE__) << nodec::logging::Level::Debug;
-}
\ No newline at end of file
diff --git a/tests/logging/test_basic/sub.cpp b/tests/logging/test_basic/sub.cpp
deleted file mode 100644
index f0853bbe..00000000
--- a/tests/logging/test_basic/sub.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#include "sub.hpp"
-
-#include
-
-void sub_func()
-{
- nodec::logging::info("this message sent from sub_func(). Can you see it?", __FILE__, __LINE__);
-}
\ No newline at end of file
diff --git a/tests/logging/test_basic/sub.hpp b/tests/logging/test_basic/sub.hpp
deleted file mode 100644
index c2aa8f9e..00000000
--- a/tests/logging/test_basic/sub.hpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef SUB_HPP_
-#define SUB_HPP_
-
-void sub_func();
-
-#endif
\ No newline at end of file
diff --git a/tests/optional/basic.cpp b/tests/optional/basic.cpp
index 3ba2bc2d..13de27f8 100644
--- a/tests/optional/basic.cpp
+++ b/tests/optional/basic.cpp
@@ -3,7 +3,6 @@
//#include
-#include
#include
#include