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