* Moved and refactored code from old project cpputilsmaster
| @@ -0,0 +1,3 @@ | |||||
| [submodule "/home/bergmann/projects/TotoStarOnline/projects/worker/cpplogging/cmake/modules"] | |||||
| path = /home/bergmann/projects/TotoStarOnline/projects/worker/cpplogging/cmake/modules | |||||
| url = b3rgmann@git.bergmann89.de:cpp/CmakeModules.git | |||||
| @@ -0,0 +1,53 @@ | |||||
| # Initialize CMake ################################################################################ | |||||
| CMake_Minimum_Required ( VERSION 3.12.0 FATAL_ERROR ) | |||||
| # Set CMAKE_BUILD_TYPE | |||||
| If ( NOT CMAKE_BUILD_TYPE ) | |||||
| Set ( CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build!" FORCE ) | |||||
| EndIf ( NOT CMAKE_BUILD_TYPE ) | |||||
| Set_Property ( CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel ) | |||||
| # Set CMAKE_MODULE_PATH | |||||
| If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) | |||||
| Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} | |||||
| "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) | |||||
| EndIf ( ) | |||||
| If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) | |||||
| Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} | |||||
| "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) | |||||
| EndIf ( ) | |||||
| # Project ######################################################################################### | |||||
| Include ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpplogging-var.cmake" ) | |||||
| Project ( cpplogging | |||||
| DESCRIPTION "A simple library" | |||||
| VERSION "${CPPLOGGING_VERSION}" ) | |||||
| Include ( CTest ) | |||||
| Include ( GNUInstallDirs ) | |||||
| # Subdirectories | |||||
| Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/src ) | |||||
| Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/test ) | |||||
| # Install | |||||
| Include ( CMakePackageConfigHelpers ) | |||||
| Write_Basic_Package_Version_File ( "${CMAKE_CURRENT_BINARY_DIR}/cmake/cpplogging-config-version.cmake" | |||||
| VERSION ${CPPLOGGING_VERSION} | |||||
| COMPATIBILITY AnyNewerVersion ) | |||||
| Configure_File ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpplogging-config.cmake" | |||||
| "${CMAKE_CURRENT_BINARY_DIR}/cmake/cpplogging-config.cmake" | |||||
| @ONLY ) | |||||
| Set ( ConfigPackageLocation "${CPPLOGGING_INSTALL_DIR_SHARE}/cmake" ) | |||||
| Install ( EXPORT cpplogging | |||||
| NAMESPACE cpplogging:: | |||||
| DESTINATION ${ConfigPackageLocation} ) | |||||
| Install ( FILES | |||||
| "${CMAKE_CURRENT_BINARY_DIR}/cmake/cpplogging-config.cmake" | |||||
| "${CMAKE_CURRENT_BINARY_DIR}/cmake/cpplogging-config-version.cmake" | |||||
| DESTINATION | |||||
| ${ConfigPackageLocation} | |||||
| COMPONENT | |||||
| Devel ) | |||||
| @@ -0,0 +1,7 @@ | |||||
| # cpplogging-config.cmake - package configuration file | |||||
| Get_Filename_Component ( CURRENT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH ) | |||||
| Include ( ${CURRENT_DIR}/cpplogging.cmake ) | |||||
| Include ( CMakeFindDependencyMacro ) | |||||
| Find_Dependency ( cppcore ) | |||||
| @@ -0,0 +1,20 @@ | |||||
| # Version | |||||
| Set ( CPPLOGGING_VERSION_MAJOR 1 ) | |||||
| Set ( CPPLOGGING_VERSION_MINOR 0 ) | |||||
| Set ( CPPLOGGING_VERSION_PATCH 0 ) | |||||
| Set ( CPPLOGGING_VERSION_BUILD 0 ) | |||||
| Set ( CPPLOGGING_VERSION_SHORT "${CPPLOGGING_VERSION_MAJOR}.${CPPLOGGING_VERSION_MINOR}" ) | |||||
| Set ( CPPLOGGING_VERSION "${CPPLOGGING_VERSION_SHORT}.${CPPLOGGING_VERSION_PATCH}.${CPPLOGGING_VERSION_BUILD}" ) | |||||
| Set ( CPPLOGGING_NAME "cpplogging-${CPPLOGGING_VERSION_SHORT}" ) | |||||
| Set ( CPPLOGGING_OUTPUTNAME "helloworld" ) | |||||
| # Install directories | |||||
| Set ( CPPLOGGING_INSTALL_DIR_INCLUDE "include/${CPPLOGGING_NAME}" ) | |||||
| Set ( CPPLOGGING_INSTALL_DIR_LIB "lib" ) | |||||
| Set ( CPPLOGGING_INSTALL_DIR_SHARE "share/${CPPLOGGING_NAME}" ) | |||||
| # C Standard | |||||
| Set ( CMAKE_C_STANDARD 11 ) | |||||
| Set ( CMAKE_CXX_STANDARD 17 ) | |||||
| Set ( CMAKE_C_STANDARD_REQUIRED ON ) | |||||
| Set ( CMAKE_CXX_STANDARD_REQUIRED ON ) | |||||
| @@ -0,0 +1 @@ | |||||
| Subproject commit 1a32531aef2deeebd5637b1873bc4e976628801c | |||||
| @@ -0,0 +1,4 @@ | |||||
| #pragma once | |||||
| #include "interface/logger.h" | |||||
| #include "interface/types.h" | |||||
| @@ -0,0 +1,197 @@ | |||||
| #pragma once | |||||
| #include <sstream> | |||||
| #include <cpplogging/types.h> | |||||
| #define cpplogging_log(p_logger, p_level) \ | |||||
| if (p_logger.is_enabled(::cpplogging::log_level::p_level)) \ | |||||
| p_logger.log() \ | |||||
| .level(::cpplogging::log_level::p_level) \ | |||||
| .file(__FILE__) \ | |||||
| .line(__LINE__) | |||||
| namespace cpplogging | |||||
| { | |||||
| struct logger; | |||||
| namespace __impl | |||||
| { | |||||
| /** | |||||
| * @brief Helper class to create a log entry. | |||||
| */ | |||||
| struct log_helper | |||||
| : public std::ostringstream | |||||
| { | |||||
| private: | |||||
| logger& _logger; | |||||
| log_entry_ptr_s _entry; | |||||
| private: | |||||
| /** | |||||
| * @brief Move constructor. | |||||
| */ | |||||
| log_helper(log_helper&&) = delete; | |||||
| /** | |||||
| * @brief Copy constructor. | |||||
| */ | |||||
| log_helper(const log_helper&) = delete; | |||||
| public: | |||||
| /** | |||||
| * @brief Constructor. | |||||
| * | |||||
| * @param[in] p_logger Logger this log helper belongs to. | |||||
| */ | |||||
| inline log_helper(logger& p_logger); | |||||
| /** | |||||
| * @brief Constructor. | |||||
| * | |||||
| * @param[in] p_logger Logger this log helper belongs to. | |||||
| * @param[in] p_entry Log entry to create. | |||||
| */ | |||||
| inline log_helper(logger& p_logger, const log_entry_ptr_s& p_entry); | |||||
| /** | |||||
| * @brief Destructor. Will finally write the log entry. | |||||
| */ | |||||
| inline ~log_helper(); | |||||
| /** | |||||
| * @brief Set the log level of the entry, | |||||
| */ | |||||
| inline log_helper& level(log_level level); | |||||
| /** | |||||
| * @brief Set the sender of the log entry. | |||||
| */ | |||||
| inline log_helper& sender(const void * sender); | |||||
| /** | |||||
| * @brief Set the filename of the log entry. | |||||
| */ | |||||
| inline log_helper& file(const char * file); | |||||
| /** | |||||
| * @brief Set the line number of the log entry. | |||||
| */ | |||||
| inline log_helper& line(int line); | |||||
| /** | |||||
| * @brief Set the message of the log entry. | |||||
| */ | |||||
| inline log_helper& message(const std::string& msg); | |||||
| /** | |||||
| * @brief Add the passed string to the log message. | |||||
| */ | |||||
| inline log_helper& add_message(const std::string& msg); | |||||
| /** | |||||
| * @brief Set the message of the entry using format string. | |||||
| */ | |||||
| template<typename... T_args> | |||||
| inline log_helper& format(const std::string& fmt, T_args&&... args); | |||||
| /** | |||||
| * @brief Add a message to the entry using format string. | |||||
| */ | |||||
| template<typename... T_args> | |||||
| inline log_helper& add_format(const std::string& fmt, T_args&&... args); | |||||
| private: | |||||
| /** | |||||
| * @brief Format a string. | |||||
| * | |||||
| * @tparam T_args Arguments of the format string. | |||||
| * | |||||
| * @param[in] fmt Format string. | |||||
| * @param[in] sz Size of the buffer to use for the formatting. | |||||
| * @param[in] args Arguments of the format. | |||||
| */ | |||||
| template<typename... T_args> | |||||
| static inline std::string format(const std::string& fmt, size_t sz, T_args&&... args); | |||||
| }; | |||||
| } | |||||
| /** | |||||
| * @brief Class to create log messages. | |||||
| */ | |||||
| struct logger | |||||
| { | |||||
| public: | |||||
| friend __impl::log_helper; | |||||
| private: | |||||
| std::string _name; //!< name of the logger | |||||
| private: | |||||
| /** | |||||
| * @brief Move constructor. | |||||
| */ | |||||
| logger(logger&&) = delete; | |||||
| /** | |||||
| * @brief Copy constructor. | |||||
| */ | |||||
| logger(const logger&) = delete; | |||||
| public: | |||||
| /** | |||||
| * @brief Constructor. | |||||
| */ | |||||
| inline logger(const std::string name); | |||||
| /** | |||||
| * @brief Destructor. | |||||
| */ | |||||
| virtual ~logger() = default; | |||||
| /** | |||||
| * @brief Get the name of the logger. | |||||
| */ | |||||
| inline const std::string& name() const; | |||||
| /** | |||||
| * @brief Check if the passed log level is enabled. | |||||
| * | |||||
| * @param[in] level Level to check. | |||||
| * | |||||
| * @retval true The passed log level is enabled. | |||||
| * @retval false The passed log level is disabled. | |||||
| */ | |||||
| virtual bool is_enabled(log_level level) const = 0; | |||||
| /** | |||||
| * @brief Write a log entry, | |||||
| * | |||||
| * @return Log helper to create log entry. | |||||
| */ | |||||
| inline __impl::log_helper log(); | |||||
| protected: | |||||
| /** | |||||
| * @brief Write a log entry. | |||||
| * | |||||
| * @param[in] entry Log entry to write. | |||||
| */ | |||||
| virtual void write_entry(const log_entry_ptr_s& entry) const = 0; | |||||
| public: | |||||
| /** | |||||
| * @brief Get the instance on an logger with the passed name. | |||||
| * | |||||
| * @param[in] name Name of the loggger. | |||||
| * | |||||
| * @return Instance of the requested logger. | |||||
| */ | |||||
| static logger& get(const std::string& name = ""); | |||||
| }; | |||||
| } | |||||
| #include "logger.inl" | |||||
| @@ -0,0 +1,118 @@ | |||||
| #pragma once | |||||
| #include "logger.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| namespace __impl | |||||
| { | |||||
| /* log_helper */ | |||||
| log_helper | |||||
| ::log_helper(logger& p_logger) | |||||
| : _logger (p_logger) | |||||
| , _entry (new log_entry()) | |||||
| { _entry->name = p_logger.name(); } | |||||
| log_helper | |||||
| ::log_helper(logger& p_logger, const log_entry_ptr_s& p_entry) | |||||
| : _logger (p_logger) | |||||
| , _entry (p_entry) | |||||
| { _entry->name = p_logger.name(); } | |||||
| log_helper | |||||
| ::~log_helper() | |||||
| { | |||||
| _entry->message += str(); | |||||
| _logger.write_entry(_entry); | |||||
| } | |||||
| log_helper& log_helper | |||||
| ::level(log_level level) | |||||
| { | |||||
| _entry->level = level; | |||||
| return *this; | |||||
| } | |||||
| log_helper& log_helper | |||||
| ::sender(const void * sender) | |||||
| { | |||||
| _entry->sender = sender; | |||||
| return *this; | |||||
| } | |||||
| log_helper& log_helper | |||||
| ::file(const char * file) | |||||
| { | |||||
| _entry->file = file; | |||||
| return *this; | |||||
| } | |||||
| log_helper& log_helper | |||||
| ::line(int line) | |||||
| { | |||||
| _entry->line = line; | |||||
| return *this; | |||||
| } | |||||
| log_helper& log_helper | |||||
| ::message(const std::string& msg) | |||||
| { | |||||
| _entry->message = msg; | |||||
| return *this; | |||||
| } | |||||
| log_helper& log_helper | |||||
| ::add_message(const std::string& msg) | |||||
| { | |||||
| _entry->message += msg; | |||||
| return *this; | |||||
| } | |||||
| template<typename... T_args> | |||||
| log_helper& log_helper | |||||
| ::format(const std::string& fmt, T_args&&... args) | |||||
| { | |||||
| _entry->message = format(fmt, 0x8000, std::forward<T_args>(args)...); | |||||
| return *this; | |||||
| } | |||||
| template<typename... T_args> | |||||
| log_helper& log_helper | |||||
| ::add_format(const std::string& fmt, T_args&&... args) | |||||
| { | |||||
| _entry->message += format(fmt, 0x8000, std::forward<T_args>(args)...); | |||||
| return *this; | |||||
| } | |||||
| template<typename... T_args> | |||||
| std::string log_helper | |||||
| ::format(const std::string& fmt, size_t sz, T_args&&... args) | |||||
| { | |||||
| std::unique_ptr<char, decltype(&free)> buff(static_cast<char*>(malloc(sz)), &free); | |||||
| auto len = snprintf(buff.get(), sz, fmt.c_str(), std::forward<T_args>(args)...); | |||||
| if (len < 0) | |||||
| throw std::runtime_error("unable to format string"); | |||||
| return std::string(buff.get(), len); | |||||
| } | |||||
| } | |||||
| /* logger */ | |||||
| logger | |||||
| ::logger(const std::string name) | |||||
| : _name(name) | |||||
| { } | |||||
| const std::string& logger | |||||
| ::name() const | |||||
| { return _name; } | |||||
| __impl::log_helper logger | |||||
| ::log() | |||||
| { return __impl::log_helper(*this); } | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| #pragma once | |||||
| #include "manager/manager.h" | |||||
| #include "manager/consumer.h" | |||||
| #include "manager/logger_impl.h" | |||||
| #include "manager/matcher.h" | |||||
| #include "manager/rule.h" | |||||
| @@ -0,0 +1,6 @@ | |||||
| #pragma once | |||||
| #include "consumer/consumer.h" | |||||
| #include "consumer/consumer_stream.h" | |||||
| #include "consumer/consumer.inl" | |||||
| @@ -0,0 +1,82 @@ | |||||
| #pragma once | |||||
| #include <memory> | |||||
| #include <vector> | |||||
| #include <iostream> | |||||
| #include <cpplogging/types.h> | |||||
| namespace cpplogging | |||||
| { | |||||
| /** | |||||
| * @brief Class to consume log entries. | |||||
| */ | |||||
| struct consumer | |||||
| { | |||||
| private: | |||||
| using format_type = std::vector<std::string>; | |||||
| protected: | |||||
| std::string _name; //!< name of the consumer | |||||
| format_type _format; //!< parsed logging format | |||||
| /** | |||||
| * @brief Get the default log format. | |||||
| */ | |||||
| static inline const std::string& default_format(); | |||||
| /** | |||||
| * @brief Parse the given format and generate a pre-compiled list of strings. | |||||
| * | |||||
| * @param[in] format Format to parse. | |||||
| * | |||||
| * @return Precompiled format. | |||||
| */ | |||||
| static format_type parse_format(const std::string& format); | |||||
| /** | |||||
| * @brief Write a log entry to the passed stream using the precompiled format. | |||||
| * | |||||
| * @param[in] e Entry to write to stream. | |||||
| * @param[in] f Precompiled format to use for writing. | |||||
| * @param[in] os Stream to write entry to. | |||||
| */ | |||||
| static void write_formatted(const log_entry& e, const format_type& f, std::ostream& os); | |||||
| public: | |||||
| /** | |||||
| * @brief Constructor. | |||||
| * | |||||
| * @param[in] name Name of the consumer. | |||||
| * @param[in] auto_register Automatically register the consumer. | |||||
| * @param[in] format Format to use for logging. | |||||
| */ | |||||
| consumer( | |||||
| const std::string& name, | |||||
| bool auto_register, | |||||
| const std::string& format = default_format()); | |||||
| /** | |||||
| * @breif Destructor. | |||||
| */ | |||||
| virtual ~consumer(); | |||||
| /** | |||||
| * @brief Get the name of the logger. | |||||
| */ | |||||
| inline const std::string& name() const; | |||||
| /** | |||||
| * @brief Consume a log entry. | |||||
| * | |||||
| * @param[in] entry Log entry to consume. | |||||
| */ | |||||
| virtual void write_entry(const log_entry_ptr_s& entry) const = 0; | |||||
| }; | |||||
| using consumer_ptr_u = std::unique_ptr<consumer>; | |||||
| } | |||||
| #include "consumer.inl" | |||||
| @@ -0,0 +1,26 @@ | |||||
| #pragma once | |||||
| #include "consumer.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /* consumer */ | |||||
| const std::string& consumer | |||||
| ::default_format() | |||||
| { | |||||
| static const std::string value( | |||||
| "[${runtime:-016.6f}] " | |||||
| "${level:5S} " | |||||
| "${sender:016x}@${thread:016x} " | |||||
| "${filename:-25}:${line:5d}$n" | |||||
| "${message}"); | |||||
| return value; | |||||
| } | |||||
| const std::string& consumer | |||||
| ::name() const | |||||
| { return _name; } | |||||
| } | |||||
| @@ -0,0 +1,52 @@ | |||||
| #pragma once | |||||
| #include <mutex> | |||||
| #include "consumer.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /** | |||||
| * @brief Consumer that writes the log entries to a stream, | |||||
| */ | |||||
| struct consumer_stream | |||||
| : public consumer | |||||
| { | |||||
| private: | |||||
| using stream_ptr_u = std::unique_ptr<std::ostream>; | |||||
| private: | |||||
| mutable std::mutex _mutex; //!< Mutex to protect the stream. | |||||
| std::ostream& _stream; //!< Stream to write log entries to. | |||||
| stream_ptr_u _storage; //!< Pointer to store owned stream. | |||||
| public: | |||||
| /** | |||||
| * @brief Constructor. | |||||
| * | |||||
| * @param[in] name Name of the consumer. | |||||
| * @param[in] stream Stream to write data to. | |||||
| * @param[in] auto_register Automatically register the consumer. | |||||
| */ | |||||
| consumer_stream(const std::string& name, std::ostream& stream, bool auto_register); | |||||
| /** | |||||
| * @brief Constructor. | |||||
| * | |||||
| * @param[in] name Name of the consumer. | |||||
| * @param[in] stream Stream to write data to. | |||||
| * @param[in] auto_register Automatically register the consumer. | |||||
| */ | |||||
| consumer_stream(const std::string& name, stream_ptr_u&& stream, bool auto_register); | |||||
| /** | |||||
| * @brief Consume a log entry. | |||||
| * | |||||
| * @param[in] entry Log entry to consume. | |||||
| */ | |||||
| void write_entry(const log_entry_ptr_s& entry) const override; | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,62 @@ | |||||
| #pragma once | |||||
| #include <set> | |||||
| #include <mutex> | |||||
| #include "rule.h" | |||||
| #include "../interface/logger.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| struct logger_impl | |||||
| : public logger | |||||
| { | |||||
| private: | |||||
| mutable std::mutex _mutex; //!< mutex to protect the object | |||||
| std::set<rule*> _rules; //!< rules assigned to this logger | |||||
| log_level _min_level { log_level::unknown }; //!< min log level over all rules | |||||
| log_level _max_level { log_level::unknown }; //!< max log level over all rules | |||||
| public: | |||||
| using logger::logger; | |||||
| /** | |||||
| * @brief Add a rule to this logger. | |||||
| * | |||||
| * @param[in] rule Rule to add. | |||||
| */ | |||||
| void add_rule(rule& rule); | |||||
| /** | |||||
| * @brief Remove a rule to this logger. | |||||
| * | |||||
| * @param[in] rule Rule to remove. | |||||
| */ | |||||
| void remove_rule(rule& rule); | |||||
| /** | |||||
| * @brief Check if the passed log level is enabled. | |||||
| * | |||||
| * @param[in] level Level to check. | |||||
| * | |||||
| * @retval true The passed log level is enabled. | |||||
| * @retval false The passed log level is disabled. | |||||
| */ | |||||
| bool is_enabled(log_level level) const override; | |||||
| protected: | |||||
| /** | |||||
| * @brief Write a log entry. | |||||
| * | |||||
| * @param[in] entry Log entry to write. | |||||
| */ | |||||
| void write_entry(const log_entry_ptr_s& entry) const override; | |||||
| /** | |||||
| * @brief Update the log levels cached in the logger. | |||||
| */ | |||||
| void update_log_level(); | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,158 @@ | |||||
| #pragma once | |||||
| #include <set> | |||||
| #include <map> | |||||
| #include <list> | |||||
| #include "rule.h" | |||||
| #include "logger_impl.h" | |||||
| #include "consumer/consumer.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /** | |||||
| * @brief Wrapps the static configuration methods for the logger engine. | |||||
| * | |||||
| * The logging engine has two modes: logging and config. | |||||
| * | |||||
| * In the config mode, the user can add rules, consumer and matcher. | |||||
| * In the logging mode the engine is not thread-safe and must be accessed | |||||
| * from only one thread! | |||||
| * | |||||
| * In the logging mode the user can only create new loggers or log messages. | |||||
| * The logging mode is thread-safe. The user can use as many thread as he want, | |||||
| * to log messages. | |||||
| */ | |||||
| struct manager | |||||
| { | |||||
| private: | |||||
| struct consumer_wrapper | |||||
| { | |||||
| consumer& _consumer; //!< consumer to use for operations | |||||
| consumer_ptr_u _storage; //!< storage to store owner object | |||||
| /** | |||||
| * @brief Construtor to create a consumer wrapper by using the consumer reference. | |||||
| */ | |||||
| inline consumer_wrapper(consumer& p_consumer); | |||||
| /** | |||||
| * @brief Construtor to create a consumer wrapper. The wrapper takes the ownership of the consumer. | |||||
| */ | |||||
| inline consumer_wrapper(consumer_ptr_u&& p_consumer); | |||||
| /** | |||||
| * @brief Operator to compare the wrapper to a other wrapper | |||||
| */ | |||||
| inline bool operator<(const consumer_wrapper& other) const; | |||||
| /** | |||||
| * @brief Operator to access the consumer behind the wrapper. | |||||
| */ | |||||
| inline consumer* operator->(); | |||||
| /** | |||||
| * @brief Operator to access the consumer behind the wrapper. | |||||
| */ | |||||
| inline const consumer* operator->() const; | |||||
| /** | |||||
| * @brief Operator to access the consumer behind the wrapper. | |||||
| */ | |||||
| inline consumer& operator*(); | |||||
| /** | |||||
| * @brief Operator to access the consumer behind the wrapper. | |||||
| */ | |||||
| inline const consumer& operator*() const; | |||||
| }; | |||||
| using logger_impl_ptr_u = std::unique_ptr<logger_impl>; | |||||
| using logger_map = std::map<std::string, logger_impl_ptr_u>; | |||||
| using rule_list = std::list<rule>; | |||||
| using consumer_set = std::set<consumer_wrapper>; | |||||
| private: | |||||
| static std::mutex _mutex; //!< mutex to protect the engine | |||||
| static logger_impl _default; //!< default logger | |||||
| static logger_map _logger; //!< normal logger instances | |||||
| static rule_list _rules; //!< rules to combine loggers with consumers | |||||
| static consumer_set _consumer; //!< consumers registered | |||||
| public: | |||||
| /** | |||||
| * @brief Get the instance of the logger with the requested name. | |||||
| * | |||||
| * @param[in] name Name of the logger to get the instance for. | |||||
| * | |||||
| * @return Instance of the logger with the requested name. | |||||
| */ | |||||
| static logger& get_logger(const std::string& name = ""); | |||||
| /** | |||||
| * @brief Register a new consumer in the logging framework. | |||||
| * | |||||
| * @param[in] consumer Reference of the consumer to register. | |||||
| */ | |||||
| static void register_consumer(consumer& consumer); | |||||
| /** | |||||
| * @brief Register a new consumer in the logging framework. | |||||
| * | |||||
| * @param[in] consumer Consumer to register (the logging framework will own the consumer). | |||||
| * | |||||
| * @return Pointer to the consumer. | |||||
| */ | |||||
| static const consumer* register_consumer(consumer_ptr_u&& consumer); | |||||
| /** | |||||
| * @brief Unregister a consumer from the logging framework. | |||||
| * | |||||
| * @param[in] consumer Reference of the consumer to unregister | |||||
| */ | |||||
| static void unregister_consumer(consumer& consumer); | |||||
| /** | |||||
| * @brief Unregister a consumer from the logging framework. | |||||
| * | |||||
| * @param[in] consumer Consumer to unregister from the framework. | |||||
| */ | |||||
| static void unregister_consumer(consumer* consumer); | |||||
| /** | |||||
| * @brief Define a new rule in the logging framework. | |||||
| * | |||||
| * @param[in] logger_matcher Matcher to use for matching loggers. | |||||
| * @param[in] consumer_matcher Matcher to use for matching consumers. | |||||
| * @param[in] min_level Minimum log level. | |||||
| * @param[in] max_level Maximum log level. | |||||
| * | |||||
| * @return Return the handle to the rule (for removing). | |||||
| */ | |||||
| static rule_handle define_rule( | |||||
| matcher_ptr_u&& logger_matcher, | |||||
| matcher_ptr_u&& consumer_matcher, | |||||
| log_level min_level = log_level::debug, | |||||
| log_level max_level = log_level::error); | |||||
| /** | |||||
| * @brief Remove a rule from the logging framework. | |||||
| * | |||||
| * @param[in] handle Handle of the rule to remove. | |||||
| */ | |||||
| static void undefine_rule(rule_handle handle); | |||||
| /** | |||||
| * @brief Remove all consumers, rules, matchers and loggers. | |||||
| */ | |||||
| static void reset(); | |||||
| private: | |||||
| /** | |||||
| * @brief Initialize the given logger instance. | |||||
| */ | |||||
| static logger_impl& init_logger(logger_impl& logger); | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| #pragma once | |||||
| #include <exception> | |||||
| #include "manager.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /* manager::consumer_wrapper */ | |||||
| manager::consumer_wrapper | |||||
| ::consumer_wrapper(consumer& p_consumer) | |||||
| : _consumer (p_consumer) | |||||
| , _storage (nullptr) | |||||
| { } | |||||
| manager::consumer_wrapper | |||||
| ::consumer_wrapper(consumer_ptr_u&& p_consumer) | |||||
| : _consumer (*p_consumer) | |||||
| , _storage (std::move(p_consumer)) | |||||
| { } | |||||
| bool manager::consumer_wrapper | |||||
| ::operator<(const consumer_wrapper& other) const | |||||
| { return &_consumer < &other._consumer; } | |||||
| consumer* manager::consumer_wrapper | |||||
| ::operator->() | |||||
| { return &_consumer; } | |||||
| const consumer* manager::consumer_wrapper | |||||
| ::operator->() const | |||||
| { return &_consumer; } | |||||
| consumer& manager::consumer_wrapper | |||||
| ::operator*() | |||||
| { return _consumer; } | |||||
| const consumer& manager::consumer_wrapper | |||||
| ::operator*() const | |||||
| { return _consumer; } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| #pragma once | |||||
| #include "matcher/matcher.h" | |||||
| #include "matcher/matcher_all.h" | |||||
| #include "matcher/matcher_default_logger.h" | |||||
| #include "matcher/matcher_regex.h" | |||||
| @@ -0,0 +1,45 @@ | |||||
| #pragma once | |||||
| #include <memory> | |||||
| #include "../consumer/consumer.h" | |||||
| #include "../../interface/logger.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /** | |||||
| * @brief Class to use for matching a rule or a logger. | |||||
| */ | |||||
| struct matcher | |||||
| { | |||||
| /** | |||||
| * @brief Destructor. | |||||
| */ | |||||
| virtual ~matcher() = default; | |||||
| /** | |||||
| * @brief Check if the passed logger match this matcher. | |||||
| * | |||||
| * @param[in] logger Logger to check. | |||||
| * | |||||
| * @retval true If the logger matches. | |||||
| * @retval false If the logger does not match. | |||||
| */ | |||||
| virtual bool match(const logger& logger) const; | |||||
| /** | |||||
| * @brief Check if the passed consumer match this matcher. | |||||
| * | |||||
| * @param[in] consumer Consumer to check. | |||||
| * | |||||
| * @retval true If the consumer matches. | |||||
| * @retval false If the consumer does not match. | |||||
| */ | |||||
| virtual bool match(const consumer& consumer) const; | |||||
| }; | |||||
| using matcher_ptr_u = std::unique_ptr<matcher>; | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| #pragma once | |||||
| #include "matcher.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /** | |||||
| * @brief This matcher matches any logger and any consumer. | |||||
| */ | |||||
| struct matcher_all | |||||
| : public matcher | |||||
| { | |||||
| /** | |||||
| * @brief Check if the passed logger match this matcher. | |||||
| * | |||||
| * @param[in] logger Logger to check. | |||||
| * | |||||
| * @retval true If the logger matches. | |||||
| * @retval false If the logger does not match. | |||||
| */ | |||||
| bool match(const logger& logger) const override; | |||||
| /** | |||||
| * @brief Check if the passed consumer match this matcher. | |||||
| * | |||||
| * @param[in] consumer Consumer to check. | |||||
| * | |||||
| * @retval true If the consumer matches. | |||||
| * @retval false If the consumer does not match. | |||||
| */ | |||||
| bool match(const consumer& consumer) const override; | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| #pragma once | |||||
| #include "matcher.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /** | |||||
| * @brief This matcher does only match the default logger. | |||||
| */ | |||||
| struct matcher_default_logger | |||||
| : public matcher | |||||
| { | |||||
| private: | |||||
| logger& _default; //!< the default logger | |||||
| public: | |||||
| using matcher::match; | |||||
| /** | |||||
| * @brief Constructor. | |||||
| */ | |||||
| matcher_default_logger(); | |||||
| /** | |||||
| * @brief Check if the passed logger match this matcher. | |||||
| * | |||||
| * @param[in] logger Logger to check. | |||||
| * | |||||
| * @retval true If the logger matches. | |||||
| * @retval false If the logger does not match. | |||||
| */ | |||||
| bool match(const logger& logger) const override; | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,50 @@ | |||||
| #pragma once | |||||
| #include <regex> | |||||
| #include "matcher.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /** | |||||
| * @brief Matcher that uses a regular expression to match a logger or a consumer | |||||
| */ | |||||
| struct matcher_regex | |||||
| : public matcher | |||||
| { | |||||
| private: | |||||
| std::regex _regex; //!< regex to use for matching | |||||
| bool _invert; //!< invert the regular expression | |||||
| public: | |||||
| /** | |||||
| * @brief Constructor. | |||||
| * | |||||
| * @param[in] regex Regex to use for matching. | |||||
| * @param[in] invert Invert the regular expression. | |||||
| */ | |||||
| matcher_regex(const std::string regex, bool invert = false); | |||||
| /** | |||||
| * @brief Check if the passed logger match this matcher. | |||||
| * | |||||
| * @param[in] logger Logger to check. | |||||
| * | |||||
| * @retval true If the logger matches. | |||||
| * @retval false If the logger does not match. | |||||
| */ | |||||
| bool match(const logger& logger) const override; | |||||
| /** | |||||
| * @brief Check if the passed consumer match this matcher. | |||||
| * | |||||
| * @param[in] consumer Consumer to check. | |||||
| * | |||||
| * @retval true If the consumer matches. | |||||
| * @retval false If the consumer does not match. | |||||
| */ | |||||
| bool match(const consumer& consumer) const override; | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,70 @@ | |||||
| #pragma once | |||||
| #include <set> | |||||
| #include <mutex> | |||||
| #include "matcher/matcher.h" | |||||
| #include "consumer/consumer.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| struct rule | |||||
| { | |||||
| private: | |||||
| mutable std::mutex _mutex; | |||||
| std::set<const consumer*> _consumer; | |||||
| public: | |||||
| matcher_ptr_u logger_matcher; //!< matcher to use for logger matching | |||||
| matcher_ptr_u consumer_matcher; //!< matcher to use for consumer matching | |||||
| log_level min_level; //!< minimum log level | |||||
| log_level max_level; //!< maximum log level | |||||
| /** | |||||
| * @brief Constructor. | |||||
| * | |||||
| * @param[in] lm Matcher to use for logger matching. | |||||
| * @param[in] cm Matcher to use for consumer matching. | |||||
| * @param[in] min Minimum log level. | |||||
| * @param[in] max Maximum log level. | |||||
| */ | |||||
| inline rule(matcher_ptr_u&& lm, matcher_ptr_u&& cm, log_level min, log_level max); | |||||
| /** | |||||
| * @brief CHeck if the passed log level is enabled in this rule. | |||||
| * | |||||
| * @param[in] level Level to check. | |||||
| * | |||||
| * @retval true If the log level is enabled for this rule. | |||||
| * @retval false If the log level is not enabled for this rule. | |||||
| */ | |||||
| inline bool is_enabled(log_level level) const; | |||||
| /** | |||||
| * @brief Add a consumer to this rule. | |||||
| * | |||||
| * @param[in] consumer Consumer to add to the rule. | |||||
| */ | |||||
| inline void add_consumer(const consumer& consumer); | |||||
| /** | |||||
| * @brief Remove a consumer from this rule. | |||||
| * | |||||
| * @param[in] consumer Consumer to remove from the rule. | |||||
| */ | |||||
| inline void remove_consumer(const consumer& consumer); | |||||
| /** | |||||
| * @brief Write the passed log entry to all registered consumers. | |||||
| * | |||||
| * @param[in] entry Entry to write to the consumers. | |||||
| */ | |||||
| inline void write_entry(const log_entry_ptr_s& entry) const; | |||||
| }; | |||||
| using rule_handle = void *; | |||||
| } | |||||
| #include "rule.inl" | |||||
| @@ -0,0 +1,50 @@ | |||||
| #pragma once | |||||
| #include "rule.h" | |||||
| namespace cpplogging | |||||
| { | |||||
| /* rule */ | |||||
| rule | |||||
| ::rule(matcher_ptr_u&& lm, matcher_ptr_u&& cm, log_level min, log_level max) | |||||
| : logger_matcher (std::move(lm)) | |||||
| , consumer_matcher (std::move(cm)) | |||||
| , min_level (min) | |||||
| , max_level (max) | |||||
| { } | |||||
| bool rule | |||||
| ::is_enabled(log_level level) const | |||||
| { | |||||
| return min_level <= level | |||||
| && max_level >= level; | |||||
| } | |||||
| void rule | |||||
| ::add_consumer(const consumer& consumer) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| _consumer.insert(&consumer); | |||||
| } | |||||
| void rule | |||||
| ::remove_consumer(const consumer& consumer) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| _consumer.erase(&consumer); | |||||
| } | |||||
| void rule | |||||
| ::write_entry(const log_entry_ptr_s& entry) const | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| if (is_enabled(entry->level)) | |||||
| { | |||||
| for (auto& c : _consumer) | |||||
| c->write_entry(entry); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| #pragma once | |||||
| #include <chrono> | |||||
| #include <thread> | |||||
| #include <memory> | |||||
| namespace cpplogging | |||||
| { | |||||
| /** | |||||
| * @brief Log level. | |||||
| */ | |||||
| enum class log_level | |||||
| { | |||||
| unknown = 0, | |||||
| debug, | |||||
| info, | |||||
| warn, | |||||
| error, | |||||
| }; | |||||
| /** | |||||
| * @brief Log entry that stored all relevant information for a logged message. | |||||
| */ | |||||
| struct log_entry | |||||
| { | |||||
| using clock_type = std::chrono::steady_clock; | |||||
| using uptime_type = clock_type::time_point; | |||||
| log_level level { log_level::debug }; | |||||
| uptime_type uptime { clock_type::now() }; | |||||
| time_t systime { std::time(nullptr) }; | |||||
| const void* sender { nullptr }; | |||||
| std::thread::id thread { std::this_thread::get_id() }; | |||||
| const char* file { nullptr }; | |||||
| int line { 0 }; | |||||
| std::string name; | |||||
| std::string message; | |||||
| }; | |||||
| using log_entry_ptr_s = std::shared_ptr<log_entry>; | |||||
| } | |||||
| @@ -0,0 +1,112 @@ | |||||
| # Initialize ###################################################################################### | |||||
| Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE ) | |||||
| Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC ) | |||||
| Include ( strip_symbols OPTIONAL RESULT_VARIABLE HAS_STRIP_SYMBOLS ) | |||||
| Option ( CPPLOGGING_INSTALL_HEADER | |||||
| "Install headers of cpplogging." | |||||
| ON ) | |||||
| Option ( CPPLOGGING_INSTALL_STATIC | |||||
| "Install static library of cpplogging." | |||||
| ON ) | |||||
| Option ( CPPLOGGING_INSTALL_SHARED | |||||
| "Install shared library of cpplogging." | |||||
| ON ) | |||||
| Option ( CPPLOGGING_INSTALL_DEBUG | |||||
| "Install the stripped debug informations of cpplogging." | |||||
| OFF ) | |||||
| Option ( CPPLOGGING_NO_STRIP | |||||
| "Do not strip debug symbols from binary." | |||||
| OFF ) | |||||
| # Object Library ################################################################################## | |||||
| Set ( CMAKE_POSITION_INDEPENDENT_CODE ON ) | |||||
| Set ( CPPLOGGING_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include ) | |||||
| File ( GLOB_RECURSE CPPLOGGING_HEADER_FILES ${CPPLOGGING_INCLUDE_DIR}/*.h ) | |||||
| File ( GLOB_RECURSE CPPLOGGING_INLINE_FILES ${CPPLOGGING_INCLUDE_DIR}/*.inl ) | |||||
| File ( GLOB_RECURSE CPPLOGGING_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ) | |||||
| Add_Library ( cpplogging-objects | |||||
| OBJECT | |||||
| ${CPPLOGGING_HEADER_FILES} | |||||
| ${CPPLOGGING_INLINE_FILES} | |||||
| ${CPPLOGGING_SOURCE_FILES} ) | |||||
| Target_Include_Directories ( cpplogging-objects | |||||
| PUBLIC | |||||
| $<BUILD_INTERFACE:${CPPLOGGING_INCLUDE_DIR}> | |||||
| $<INSTALL_INTERFACE:${CPPLOGGING_INSTALL_DIR_INCLUDE}> ) | |||||
| # Static Library ################################################################################## | |||||
| Add_Library ( cpplogging-static STATIC $<TARGET_OBJECTS:cpplogging-objects> ) | |||||
| Set_Target_Properties ( cpplogging-static | |||||
| PROPERTIES | |||||
| OUTPUT_NAME "cpplogging" | |||||
| VERSION ${CPPLOGGING_VERSION} ) | |||||
| Target_Include_Directories ( cpplogging-static | |||||
| PUBLIC | |||||
| $<BUILD_INTERFACE:${CPPLOGGING_INCLUDE_DIR}> | |||||
| $<INSTALL_INTERFACE:${CPPLOGGING_INSTALL_DIR_INCLUDE}> ) | |||||
| # Shared Library ################################################################################## | |||||
| Add_Library ( cpplogging-shared SHARED $<TARGET_OBJECTS:cpplogging-objects> ) | |||||
| Set_Target_Properties ( cpplogging-shared | |||||
| PROPERTIES | |||||
| OUTPUT_NAME "cpplogging" | |||||
| VERSION ${CPPLOGGING_VERSION} | |||||
| SOVERSION ${CPPLOGGING_VERSION_SHORT} ) | |||||
| Target_Include_Directories ( cpplogging-shared | |||||
| PUBLIC | |||||
| $<BUILD_INTERFACE:${CPPLOGGING_INCLUDE_DIR}> | |||||
| $<INSTALL_INTERFACE:${CPPLOGGING_INSTALL_DIR_INCLUDE}> ) | |||||
| # Optimization #################################################################################### | |||||
| # pedantic | |||||
| If ( HAS_PEDANTIC ) | |||||
| Pedantic_Apply_Flags_Target ( cpplogging-objects CXX ) | |||||
| Pedantic_Apply_Flags_Target ( cpplogging-static CXX ) | |||||
| Pedantic_Apply_Flags_Target ( cpplogging-shared CXX ) | |||||
| EndIf ( ) | |||||
| # cotire | |||||
| If ( HAS_COTIRE ) | |||||
| Cotire ( cpplogging-objects ) | |||||
| Cotire ( cpplogging-static ) | |||||
| Cotire ( cpplogging-shared ) | |||||
| EndIf ( ) | |||||
| # Install ######################################################################################### | |||||
| # Header | |||||
| If ( CPPLOGGING_INSTALL_HEADER ) | |||||
| Install ( FILES ${CPPLOGGING_INCLUDE_DIR}/cpplogging.h | |||||
| DESTINATION ${CPPLOGGING_INSTALL_DIR_INCLUDE} ) | |||||
| Install ( DIRECTORY ${CPPLOGGING_INCLUDE_DIR}/cpplogging | |||||
| DESTINATION ${CPPLOGGING_INSTALL_DIR_INCLUDE} ) | |||||
| EndIf ( ) | |||||
| # Static | |||||
| If ( CPPLOGGING_INSTALL_STATIC ) | |||||
| Install ( TARGETS cpplogging-static | |||||
| EXPORT cpplogging | |||||
| DESTINATION ${CPPLOGGING_INSTALL_DIR_LIB} ) | |||||
| EndIf ( ) | |||||
| # Shared | |||||
| If ( CPPLOGGING_INSTALL_SHARED ) | |||||
| Install ( TARGETS cpplogging-shared | |||||
| EXPORT cpplogging | |||||
| DESTINATION ${CPPLOGGING_INSTALL_DIR_LIB} ) | |||||
| EndIf ( ) | |||||
| # Debug | |||||
| If ( HAS_STRIP_SYMBOLS AND NOT CPPLOGGING_NO_STRIP ) | |||||
| Strip_Symbols ( cpplogging-shared CPPLOGGING_DBG_FILE ) | |||||
| If ( CPPLOGGING_INSTALL_DEBUG ) | |||||
| Install ( FILES ${CPPLOGGING_DBG_FILE} | |||||
| DESTINATION ${CPPLOGGING_INSTALL_DIR_LIB} ) | |||||
| EndIf ( ) | |||||
| EndIf ( ) | |||||
| @@ -0,0 +1,7 @@ | |||||
| #include <cpplogging/manager/manager.h> | |||||
| #include <cpplogging/interface/logger.h> | |||||
| using namespace ::cpplogging; | |||||
| logger& logger::get(const std::string& name) | |||||
| { return manager::get_logger(name); } | |||||
| @@ -0,0 +1,17 @@ | |||||
| #include <cpplogging/manager/manager.h> | |||||
| #include <cpplogging/manager/consumer/consumer.h> | |||||
| using namespace ::cpplogging; | |||||
| consumer::consumer(const std::string& name, bool auto_register, const std::string& format) | |||||
| : _name (name) | |||||
| , _format (parse_format(format)) | |||||
| { | |||||
| if (auto_register) | |||||
| manager::register_consumer(*this); | |||||
| } | |||||
| consumer::~consumer() | |||||
| { | |||||
| manager::unregister_consumer(this); | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| #include <cstring> | |||||
| #include <iomanip> | |||||
| #include <cpplogging/manager/manager.h> | |||||
| #include <cpplogging/manager/consumer/consumer_stream.h> | |||||
| using namespace ::cpplogging; | |||||
| consumer_stream::consumer_stream(const std::string& name, std::ostream& stream, bool auto_register) | |||||
| : consumer (name, auto_register) | |||||
| , _stream (stream) | |||||
| , _storage (nullptr) | |||||
| { } | |||||
| consumer_stream::consumer_stream(const std::string& name, stream_ptr_u&& stream, bool auto_register) | |||||
| : consumer (name, auto_register) | |||||
| , _stream (*stream) | |||||
| , _storage (std::move(stream)) | |||||
| { } | |||||
| void consumer_stream::write_entry(const log_entry_ptr_s& entry) const | |||||
| { | |||||
| std::lock_guard<std::mutex> lk(_mutex); | |||||
| write_formatted(*entry, _format, _stream); | |||||
| } | |||||
| @@ -0,0 +1,681 @@ | |||||
| #include <map> | |||||
| #include <chrono> | |||||
| #include <cstring> | |||||
| #include <iomanip> | |||||
| #include <cpplogging/manager/manager.h> | |||||
| #include <cpplogging/manager/consumer/consumer.h> | |||||
| using namespace ::cpplogging; | |||||
| enum class parser_state | |||||
| { | |||||
| is_text, //!< current pos is inside normal text | |||||
| is_token_begin, //!< current pos is in token begin (except char '$') | |||||
| is_in_token, //!< current pos is in token (everything after '{') | |||||
| is_in_format, //!< current pos is in format (everything after ':') | |||||
| is_in_format_align, //!< current pos is in format align (expect char '-') | |||||
| is_in_format_zero, //!< current pos is in format align (expect char '0') | |||||
| is_in_format_dot, //!< current pos is in format dot (expect char '.') | |||||
| is_in_format_type, //!< current pos is in format type (expect char 's', 'f', x', 'X' or 'd') | |||||
| }; | |||||
| enum class value_token | |||||
| : uint8_t | |||||
| { | |||||
| unknown = 0, //!< unknown | |||||
| level, //!< log level | |||||
| uptime, //!< system uptime | |||||
| systime, //!< unix timestamp | |||||
| runtime, //!< runtime of the application | |||||
| sender, //!< sender of the log message | |||||
| thread, //!< thread id | |||||
| filename, //!< filename only | |||||
| filepath, //!< whole filepath | |||||
| line, //!< line number | |||||
| name, //!< name of the logger | |||||
| message, //!< log message | |||||
| newline, //!< write new line | |||||
| }; | |||||
| enum class value_format | |||||
| : uint8_t | |||||
| { | |||||
| unknown = 0, | |||||
| decimal, | |||||
| real, | |||||
| hex_lower, | |||||
| hex_upper, | |||||
| string, | |||||
| string_upper, | |||||
| }; | |||||
| struct format_item | |||||
| { | |||||
| const uint16_t magic { 0 }; //!< magic value to identify the item | |||||
| value_token token { value_token::unknown }; //!< value to print | |||||
| value_format format { value_format::unknown }; //!< format to print the value with | |||||
| uint8_t width { 0 }; //!< with of the value | |||||
| uint8_t precision { 0 }; //!< precision (for float values) | |||||
| bool right_align { false }; //!< use right alignment | |||||
| bool zero_fill { false }; //!< fill with zeros (if not than spaces) | |||||
| inline void reset() | |||||
| { memset(this, 0, sizeof(*this)); } | |||||
| inline std::string as_string() const | |||||
| { return std::string(reinterpret_cast<const char*>(this), sizeof(*this)); } | |||||
| } __attribute__ ((packed)); | |||||
| static_assert(sizeof(format_item) == 8, "format_item has invalid size!"); | |||||
| struct op_less_invariant_string | |||||
| { | |||||
| inline bool operator()(const std::string& lhs, const std::string& rhs) const | |||||
| { | |||||
| auto c1 = lhs.c_str(); | |||||
| auto c2 = rhs.c_str(); | |||||
| auto l1 = lhs.size(); | |||||
| auto l2 = rhs.size(); | |||||
| while (l1 > 0 && l2 > 0 && std::tolower(*c1) == std::tolower(*c2)) | |||||
| { | |||||
| ++c1; | |||||
| ++c2; | |||||
| --l1; | |||||
| --l2; | |||||
| } | |||||
| return l1 > 0 && l2 > 0 | |||||
| ? std::tolower(*c1) < std::tolower(*c2) | |||||
| : l1 < l2; | |||||
| } | |||||
| }; | |||||
| struct invariant_string_map | |||||
| : public std::map<std::string, value_token, op_less_invariant_string> | |||||
| { | |||||
| using map::map; | |||||
| inline value_token get_token(const std::string& token) const | |||||
| { | |||||
| auto it = find(token); | |||||
| return it == end() | |||||
| ? value_token::unknown | |||||
| : it->second; | |||||
| } | |||||
| }; | |||||
| static invariant_string_map value_token_map({ | |||||
| { "level", value_token::level }, | |||||
| { "uptime", value_token::uptime }, | |||||
| { "systime", value_token::systime }, | |||||
| { "runtime", value_token::runtime }, | |||||
| { "sender", value_token::sender }, | |||||
| { "thread", value_token::thread }, | |||||
| { "filename", value_token::filename }, | |||||
| { "filepath", value_token::filepath }, | |||||
| { "line", value_token::line }, | |||||
| { "name", value_token::name }, | |||||
| { "message", value_token::message }, | |||||
| { "newline", value_token::newline }, | |||||
| }); | |||||
| consumer::format_type consumer::parse_format(const std::string& format) | |||||
| { | |||||
| consumer::format_type ret; | |||||
| parser_state state = parser_state::is_text; | |||||
| const char * s = format.c_str(); | |||||
| const char * e = s + format.size(); | |||||
| const char * c = s; | |||||
| const char * x; | |||||
| char * tmp; | |||||
| unsigned long long int u; | |||||
| format_item item; | |||||
| while (c <= e) | |||||
| { | |||||
| switch (*c) | |||||
| { | |||||
| /* if this is the EOF */ | |||||
| case '\0': | |||||
| if (s < c) | |||||
| { | |||||
| if (!ret.empty() && !ret.back().empty() && *ret.back().c_str() != '\0') | |||||
| ret.back() += std::string(s, static_cast<size_t>(c - s - 1)); | |||||
| else | |||||
| ret.emplace_back(s, c - s); | |||||
| } | |||||
| break; | |||||
| /* the format string starts with $ */ | |||||
| case '$': | |||||
| switch (state) | |||||
| { | |||||
| /* if we are 'in_text', we switch to 'is_token_begin' */ | |||||
| case parser_state::is_text: | |||||
| state = parser_state::is_token_begin; | |||||
| break; | |||||
| /* in an other case switch back to 'is_text' */ | |||||
| default: | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* after '$' we expect '{' */ | |||||
| case '{': | |||||
| switch (state) | |||||
| { | |||||
| /* if we have seen '$' we are in 'is_token_begin' and switch to 'is_in_token' */ | |||||
| case parser_state::is_token_begin: | |||||
| if (s + 1 < c) | |||||
| { | |||||
| if (!ret.empty() && !ret.back().empty() && *ret.back().c_str() != '\0') | |||||
| ret.back() += std::string(s, static_cast<size_t>(c - s - 1)); | |||||
| else | |||||
| ret.emplace_back(s, c - s - 1); | |||||
| } | |||||
| s = c - 1; | |||||
| x = c + 1; | |||||
| state = parser_state::is_in_token; | |||||
| break; | |||||
| /* in an other case switch back to 'is_text' */ | |||||
| default: | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* inside the token we have ':' to seperate the format */ | |||||
| case ':': | |||||
| switch (state) | |||||
| { | |||||
| /* if we are in 'is_in_token' we extract the token and switch to 'is_in_format' */ | |||||
| case parser_state::is_in_token: | |||||
| state = parser_state::is_in_format; | |||||
| item.token = value_token_map.get_token(std::string(x, static_cast<size_t>(c - x))); | |||||
| if (item.token == value_token::unknown) | |||||
| { | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| } | |||||
| x = c + 1; | |||||
| break; | |||||
| /* in an other case switch back to 'is_text' */ | |||||
| default: | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* inside the format parsing we can have '-' to indicate right align */ | |||||
| case '-': | |||||
| switch (state) | |||||
| { | |||||
| /* if we are in 'is_in_format' we switch to 'is_in_format_align' */ | |||||
| case parser_state::is_in_format: | |||||
| state = parser_state::is_in_format_align; | |||||
| item.right_align = true; | |||||
| x = c + 1; | |||||
| break; | |||||
| /* in an other case switch back to 'is_text' */ | |||||
| default: | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* inside the format parsing we can have '0' to indicate zero fill */ | |||||
| case '0': | |||||
| switch (state) | |||||
| { | |||||
| /* if we are in state 'is_in_format' or 'is_in_format_align' we switch to 'is_in_format_zero' */ | |||||
| case parser_state::is_in_format: | |||||
| case parser_state::is_in_format_align: | |||||
| state = parser_state::is_in_format_zero; | |||||
| item.zero_fill = true; | |||||
| x = c + 1; | |||||
| break; | |||||
| /* in an other case switch back to 'is_text' */ | |||||
| default: | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* inside the format parsing we can have '.' to indicate the precision */ | |||||
| case '.': | |||||
| switch (state) | |||||
| { | |||||
| /* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_dot' */ | |||||
| case parser_state::is_in_format: | |||||
| case parser_state::is_in_format_align: | |||||
| case parser_state::is_in_format_zero: | |||||
| u = std::strtoull(x, &tmp, 10); | |||||
| if (x == tmp || x > c) | |||||
| { | |||||
| /* if parsing the with failed, we switch back to 'is_text' */ | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| } | |||||
| else | |||||
| { | |||||
| item.width = static_cast<uint8_t>(u); | |||||
| x = c + 1; | |||||
| state = parser_state::is_in_format_dot; | |||||
| } | |||||
| break; | |||||
| /* in an other case switch back to 'is_text' */ | |||||
| default: | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* if 'n' follows '$' we write a newline */ | |||||
| case 'n': | |||||
| switch (state) | |||||
| { | |||||
| case parser_state::is_token_begin: | |||||
| item.token = value_token::newline; | |||||
| ret.emplace_back(item.as_string()); | |||||
| item.reset(); | |||||
| state = parser_state::is_text; | |||||
| s = c + 1; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* inside the format parsing we can have 's', 'f', 'd', 'x' or 'X' to indicate the format */ | |||||
| case 's': | |||||
| case 'S': | |||||
| case 'f': | |||||
| case 'd': | |||||
| case 'x': | |||||
| case 'X': | |||||
| switch (state) | |||||
| { | |||||
| /* if we are in state 'is_in_token' everything is fine */ | |||||
| case parser_state::is_in_token: | |||||
| break; | |||||
| /* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_type' */ | |||||
| case parser_state::is_in_format: | |||||
| case parser_state::is_in_format_align: | |||||
| case parser_state::is_in_format_zero: | |||||
| case parser_state::is_in_format_dot: | |||||
| u = std::strtoull(x, &tmp, 10); | |||||
| if (x != c && (x == tmp || x > c)) | |||||
| { | |||||
| /* if parsing the with failed, we switch back to 'is_text' */ | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| } | |||||
| else | |||||
| { | |||||
| if (state == parser_state::is_in_format_dot) | |||||
| item.precision = static_cast<uint8_t>(u); | |||||
| else | |||||
| item.width = static_cast<uint8_t>(u); | |||||
| /* set the type and switch to 'is_in_format_type' */ | |||||
| x = c + 1; | |||||
| state = parser_state::is_in_format_type; | |||||
| switch(*c) | |||||
| { | |||||
| case 's': item.format = value_format::string; break; | |||||
| case 'S': item.format = value_format::string_upper; break; | |||||
| case 'f': item.format = value_format::real; break; | |||||
| case 'd': item.format = value_format::decimal; break; | |||||
| case 'x': item.format = value_format::hex_lower; break; | |||||
| case 'X': item.format = value_format::hex_upper; break; | |||||
| default: break; | |||||
| } | |||||
| } | |||||
| break; | |||||
| /* in an other case switch back to 'is_text' */ | |||||
| default: | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* inside the token/format parsing '}' indicates the end */ | |||||
| case '}': | |||||
| switch (state) | |||||
| { | |||||
| /* if we are in 'is_in_token' we extract the token, add the item and switch to 'is_text' */ | |||||
| case parser_state::is_in_token: | |||||
| state = parser_state::is_text; | |||||
| item.token = value_token_map.get_token(std::string(x, static_cast<size_t>(c - x))); | |||||
| if (item.token == value_token::unknown) | |||||
| { | |||||
| state = parser_state::is_text; | |||||
| } | |||||
| else | |||||
| { | |||||
| ret.emplace_back(item.as_string()); | |||||
| } | |||||
| item.reset(); | |||||
| s = c + 1; | |||||
| break; | |||||
| /* if we are in 'is_in_format_type' we extract the token, add the item and switch to 'is_text' */ | |||||
| case parser_state::is_in_format_type: | |||||
| state = parser_state::is_text; | |||||
| ret.emplace_back(item.as_string()); | |||||
| item.reset(); | |||||
| s = c + 1; | |||||
| break; | |||||
| /* if we are in 'is_in_format', is_in_format_zero' or 'is_in_format_align' | |||||
| we extract the with, add the item and switch to 'is_text'*/ | |||||
| case parser_state::is_in_format: | |||||
| case parser_state::is_in_format_zero: | |||||
| case parser_state::is_in_format_align: | |||||
| case parser_state::is_in_format_dot: | |||||
| u = std::strtoull(x, &tmp, 10); | |||||
| s = c + 1; | |||||
| if (x != tmp && x <= c) | |||||
| { | |||||
| if (state == parser_state::is_in_format_dot) | |||||
| item.precision = static_cast<uint8_t>(u); | |||||
| else | |||||
| item.width = static_cast<uint8_t>(u); | |||||
| ret.emplace_back(item.as_string()); | |||||
| } | |||||
| item.reset(); | |||||
| state = parser_state::is_text; | |||||
| break; | |||||
| /* in an other case switch back to 'is_text' */ | |||||
| default: | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* any non parsing relevant char */ | |||||
| default: | |||||
| switch (state) | |||||
| { | |||||
| /* in case 'is_in_format', 'is_in_format_align' and 'is_in_format_zero' | |||||
| we need to check if the current char is a digit, if not we switch to 'is_text' */ | |||||
| case parser_state::is_in_format: | |||||
| case parser_state::is_in_format_align: | |||||
| case parser_state::is_in_format_zero: | |||||
| if ( *c != '.' | |||||
| && (*c < '0' || *c > '9')) | |||||
| { | |||||
| state = parser_state::is_text; | |||||
| item.reset(); | |||||
| } | |||||
| break; | |||||
| /* any other case is fine */ | |||||
| default: | |||||
| break; | |||||
| } | |||||
| break; | |||||
| } | |||||
| ++c; | |||||
| } | |||||
| return ret; | |||||
| } | |||||
| static const auto start_time = std::chrono::steady_clock::now(); | |||||
| void consumer::write_formatted(const log_entry& e, const format_type& f, std::ostream& os) | |||||
| { | |||||
| using duration_f = std::chrono::duration<double, std::ratio<1>>; | |||||
| using duration_u = std::chrono::duration<unsigned long long int, std::ratio<1>>; | |||||
| static const std::string LogLevelUpperDebug("DEBUG", 5); | |||||
| static const std::string LogLevelUpperInfo ("INFO", 4); | |||||
| static const std::string LogLevelUpperWarn ("WARN", 4); | |||||
| static const std::string LogLevelUpperError("ERROR", 5); | |||||
| static const std::string LogLevelLowerDebug("debug", 5); | |||||
| static const std::string LogLevelLowerInfo ("info", 4); | |||||
| static const std::string LogLevelLowerWarn ("warn", 4); | |||||
| static const std::string LogLevelLowerError("error", 5); | |||||
| bool has_line_end = false; | |||||
| for (auto& x : f) | |||||
| { | |||||
| /* check if element is empty */ | |||||
| if (x.empty()) | |||||
| { | |||||
| continue; | |||||
| } | |||||
| /* check if element is normal item */ | |||||
| if (*x.c_str() != '\0') | |||||
| { | |||||
| os << x; | |||||
| continue; | |||||
| } | |||||
| /* setup the stream format */ | |||||
| const char * s; | |||||
| auto& i = *reinterpret_cast<const format_item*>(x.c_str()); | |||||
| os << (i.right_align ? std::right : std::left) | |||||
| << std::setfill(i.zero_fill ? '0' : ' ') | |||||
| << std::setw(i.width) | |||||
| << std::setprecision(i.precision); | |||||
| switch (i.token) | |||||
| { | |||||
| /* level */ | |||||
| case value_token::level: | |||||
| has_line_end = false; | |||||
| switch (i.format) | |||||
| { | |||||
| case value_format::decimal: | |||||
| os << static_cast<int>(e.level); | |||||
| break; | |||||
| case value_format::string_upper: | |||||
| switch (e.level) | |||||
| { | |||||
| case log_level::debug: os << LogLevelUpperDebug; break; | |||||
| case log_level::info: os << LogLevelUpperInfo; break; | |||||
| case log_level::warn: os << LogLevelUpperWarn; break; | |||||
| case log_level::error: os << LogLevelUpperError; break; | |||||
| default: break; | |||||
| } | |||||
| break; | |||||
| default: | |||||
| switch (e.level) | |||||
| { | |||||
| case log_level::debug: os << LogLevelLowerDebug; break; | |||||
| case log_level::info: os << LogLevelLowerInfo; break; | |||||
| case log_level::warn: os << LogLevelLowerWarn; break; | |||||
| case log_level::error: os << LogLevelLowerError; break; | |||||
| default: break; | |||||
| } | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* uptime */ | |||||
| case value_token::uptime: | |||||
| has_line_end = false; | |||||
| switch (i.format) | |||||
| { | |||||
| case value_format::decimal: | |||||
| os << std::chrono::duration_cast<duration_u>(e.uptime.time_since_epoch()).count(); | |||||
| break; | |||||
| default: | |||||
| os << std::fixed | |||||
| << std::chrono::duration_cast<duration_f>(e.uptime.time_since_epoch()).count() | |||||
| << std::dec; | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* runtime */ | |||||
| case value_token::runtime: | |||||
| has_line_end = false; | |||||
| switch (i.format) | |||||
| { | |||||
| case value_format::decimal: | |||||
| os << std::chrono::duration_cast<duration_u>(e.uptime - start_time).count(); | |||||
| break; | |||||
| default: | |||||
| os << std::fixed | |||||
| << std::chrono::duration_cast<duration_f>(e.uptime - start_time).count() | |||||
| << std::dec; | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* systemtime */ | |||||
| case value_token::systime: | |||||
| has_line_end = false; | |||||
| switch (i.format) | |||||
| { | |||||
| case value_format::decimal: | |||||
| os << e.systime; | |||||
| break; | |||||
| default: | |||||
| os << std::fixed | |||||
| << e.systime | |||||
| << std::dec; | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* sender */ | |||||
| case value_token::sender: | |||||
| has_line_end = false; | |||||
| switch (i.format) | |||||
| { | |||||
| case value_format::decimal: | |||||
| os << reinterpret_cast<uintptr_t>(e.sender); | |||||
| break; | |||||
| case value_format::hex_lower: | |||||
| os << "0x" | |||||
| << std::hex | |||||
| << reinterpret_cast<uintptr_t>(e.sender) | |||||
| << std::dec; | |||||
| break; | |||||
| default: | |||||
| os << "0x" | |||||
| << std::hex | |||||
| << std::uppercase | |||||
| << reinterpret_cast<uintptr_t>(e.sender) | |||||
| << std::nouppercase | |||||
| << std::dec; | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* thread */ | |||||
| case value_token::thread: | |||||
| has_line_end = false; | |||||
| switch (i.format) | |||||
| { | |||||
| case value_format::decimal: | |||||
| os << e.thread; | |||||
| break; | |||||
| case value_format::hex_lower: | |||||
| os << "0x" | |||||
| << std::hex | |||||
| << e.thread | |||||
| << std::dec; | |||||
| break; | |||||
| default: | |||||
| os << "0x" | |||||
| << std::hex | |||||
| << std::uppercase | |||||
| << e.thread | |||||
| << std::nouppercase | |||||
| << std::dec; | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* filename */ | |||||
| case value_token::filename: | |||||
| has_line_end = false; | |||||
| s = e.file; | |||||
| if (s) | |||||
| { | |||||
| auto tmp = strrchr(s, '/'); | |||||
| if (tmp) | |||||
| s = tmp + 1; | |||||
| } | |||||
| else | |||||
| s = "unknown"; | |||||
| os << s; | |||||
| break; | |||||
| /* filepath */ | |||||
| case value_token::filepath: | |||||
| has_line_end = false; | |||||
| os << (e.file ? e.file : "unknown"); | |||||
| break; | |||||
| /* line */ | |||||
| case value_token::line: | |||||
| has_line_end = false; | |||||
| switch (i.format) | |||||
| { | |||||
| case value_format::decimal: | |||||
| os << e.line; | |||||
| break; | |||||
| case value_format::hex_lower: | |||||
| os << "0x" | |||||
| << std::hex | |||||
| << e.line | |||||
| << std::dec; | |||||
| break; | |||||
| default: | |||||
| os << "0x" | |||||
| << std::hex | |||||
| << std::uppercase | |||||
| << e.line | |||||
| << std::nouppercase | |||||
| << std::dec; | |||||
| break; | |||||
| } | |||||
| break; | |||||
| /* name */ | |||||
| case value_token::name: | |||||
| if (!e.name.empty()) | |||||
| { | |||||
| os << e.name; | |||||
| has_line_end = (e.name.back() == '\n'); | |||||
| } | |||||
| break; | |||||
| /* message */ | |||||
| case value_token::message: | |||||
| if (!e.message.empty()) | |||||
| { | |||||
| os << e.message; | |||||
| has_line_end = (e.message.back() == '\n'); | |||||
| } | |||||
| break; | |||||
| /* newline */ | |||||
| case value_token::newline: | |||||
| has_line_end = true; | |||||
| os << std::endl; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (!has_line_end) | |||||
| os << std::endl; | |||||
| } | |||||
| @@ -0,0 +1,51 @@ | |||||
| #include <cpplogging/manager/logger_impl.h> | |||||
| using namespace ::cpplogging; | |||||
| void logger_impl::add_rule(rule& rule) | |||||
| { | |||||
| std::lock_guard<std::mutex> lk(_mutex); | |||||
| _rules.insert(&rule); | |||||
| update_log_level(); | |||||
| } | |||||
| void logger_impl::remove_rule(rule& rule) | |||||
| { | |||||
| std::lock_guard<std::mutex> lk(_mutex); | |||||
| _rules.erase(&rule); | |||||
| update_log_level(); | |||||
| } | |||||
| bool logger_impl::is_enabled(log_level level) const | |||||
| { | |||||
| return _min_level <= level | |||||
| && _max_level >= level; | |||||
| } | |||||
| void logger_impl::write_entry(const log_entry_ptr_s& entry) const | |||||
| { | |||||
| std::lock_guard<std::mutex> lk(_mutex); | |||||
| if (!is_enabled(entry->level)) | |||||
| return; | |||||
| for (auto& r : _rules) | |||||
| r->write_entry(entry); | |||||
| } | |||||
| void logger_impl::update_log_level() | |||||
| { | |||||
| _min_level = log_level::unknown; | |||||
| _max_level = log_level::unknown; | |||||
| for (auto& r : _rules) | |||||
| { | |||||
| if ( _min_level == log_level::unknown | |||||
| || _min_level > r->min_level) | |||||
| _min_level = r->min_level; | |||||
| if ( _max_level == log_level::unknown | |||||
| || _max_level < r->max_level) | |||||
| _max_level = r->max_level; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,140 @@ | |||||
| #include <cpplogging/manager/manager.h> | |||||
| #include <cpplogging/manager/manager.inl> | |||||
| using namespace ::cpplogging; | |||||
| std::mutex manager::_mutex; | |||||
| logger_impl manager::_default(""); | |||||
| manager::logger_map manager::_logger; | |||||
| manager::rule_list manager::_rules; | |||||
| manager::consumer_set manager::_consumer; | |||||
| logger& manager::get_logger(const std::string& name) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| if (name.empty()) | |||||
| return _default; | |||||
| auto it = _logger.find(name); | |||||
| if (it == _logger.end()) | |||||
| { | |||||
| it = _logger.emplace_hint(it, name, std::make_unique<logger_impl>(name)); | |||||
| manager::init_logger(*it->second); | |||||
| } | |||||
| return *it->second; | |||||
| } | |||||
| void manager::register_consumer(consumer& consumer) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| auto it = _consumer.emplace(consumer).first; | |||||
| auto& c = **it; | |||||
| for (auto& rule : _rules) | |||||
| { | |||||
| if (rule.consumer_matcher->match(c)) | |||||
| rule.add_consumer(c); | |||||
| } | |||||
| } | |||||
| const consumer* manager::register_consumer(consumer_ptr_u&& consumer) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| auto it = _consumer.emplace(std::move(consumer)).first; | |||||
| auto& ret = **it; | |||||
| for (auto& rule : _rules) | |||||
| { | |||||
| if (rule.consumer_matcher->match(ret)) | |||||
| rule.add_consumer(ret); | |||||
| } | |||||
| return &ret; | |||||
| } | |||||
| void manager::unregister_consumer(consumer& consumer) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| unregister_consumer(&consumer); | |||||
| } | |||||
| void manager::unregister_consumer(consumer* consumer) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| auto it = _consumer.find(consumer_wrapper(*consumer)); | |||||
| if (it == _consumer.end()) | |||||
| return; | |||||
| auto& c = **it; | |||||
| for (auto& rule : _rules) | |||||
| rule.remove_consumer(c); | |||||
| } | |||||
| rule_handle manager::define_rule( | |||||
| matcher_ptr_u&& logger_matcher, | |||||
| matcher_ptr_u&& consumer_matcher, | |||||
| log_level min_level, | |||||
| log_level max_level) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| /* create rule */ | |||||
| _rules.emplace_back(std::move(logger_matcher), std::move(consumer_matcher), min_level, max_level); | |||||
| auto& rule = _rules.back(); | |||||
| /* update consumers */ | |||||
| for (auto& c : _consumer) | |||||
| { | |||||
| if (rule.consumer_matcher->match(*c)) | |||||
| rule.add_consumer(*c); | |||||
| } | |||||
| /* update default logger */ | |||||
| if (rule.logger_matcher->match(_default)) | |||||
| _default.add_rule(rule); | |||||
| /* update loggers */ | |||||
| for (auto& l : _logger) | |||||
| { | |||||
| if (rule.logger_matcher->match(*l.second)) | |||||
| l.second->add_rule(rule); | |||||
| } | |||||
| return &rule; | |||||
| } | |||||
| void manager::undefine_rule(rule_handle handle) | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| /* find the rule in the list */ | |||||
| auto r = static_cast<rule*>(handle); | |||||
| auto it = _rules.begin(); | |||||
| while (&*it != r && it != _rules.end()) | |||||
| ++it; | |||||
| if (it == _rules.end()) | |||||
| return; | |||||
| /* remove the rule from the loggers */ | |||||
| _default.remove_rule(*it); | |||||
| for (auto& l : _logger) | |||||
| l.second->remove_rule(*it); | |||||
| /* remove the rule from the manager */ | |||||
| _rules.erase(it); | |||||
| } | |||||
| void manager::reset() | |||||
| { | |||||
| std::lock_guard<std::mutex> lg(_mutex); | |||||
| _logger.clear(); | |||||
| _rules.clear(); | |||||
| _consumer.clear(); | |||||
| } | |||||
| logger_impl& manager::init_logger(logger_impl& logger) | |||||
| { | |||||
| for (auto& rule : _rules) | |||||
| { | |||||
| if (rule.logger_matcher->match(logger)) | |||||
| logger.add_rule(rule); | |||||
| } | |||||
| return logger; | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| #include <cpplogging/manager/matcher/matcher.h> | |||||
| using namespace ::cpplogging; | |||||
| bool matcher::match(const logger& logger) const | |||||
| { return false; } | |||||
| bool matcher::match(const consumer& consumer) const | |||||
| { return false; } | |||||
| @@ -0,0 +1,9 @@ | |||||
| #include <cpplogging/manager/matcher/matcher_all.h> | |||||
| using namespace ::cpplogging; | |||||
| bool matcher_all::match(const logger& logger) const | |||||
| { return true; } | |||||
| bool matcher_all::match(const consumer& consumer) const | |||||
| { return true; } | |||||
| @@ -0,0 +1,11 @@ | |||||
| #include <cpplogging/manager/manager.h> | |||||
| #include <cpplogging/manager/matcher/matcher_default_logger.h> | |||||
| using namespace ::cpplogging; | |||||
| matcher_default_logger::matcher_default_logger() : | |||||
| _default(logger::get()) | |||||
| { } | |||||
| bool matcher_default_logger::match(const logger& logger) const | |||||
| { return &_default == &logger; } | |||||
| @@ -0,0 +1,14 @@ | |||||
| #include <cpplogging/manager/matcher/matcher_regex.h> | |||||
| using namespace ::cpplogging; | |||||
| matcher_regex::matcher_regex(const std::string regex, bool invert) | |||||
| : _regex (regex) | |||||
| , _invert (invert) | |||||
| { } | |||||
| bool matcher_regex::match(const logger& logger) const | |||||
| { return !logger.name().empty() && std::regex_match(logger.name(), _regex) != _invert; } | |||||
| bool matcher_regex::match(const consumer& consumer) const | |||||
| { return !consumer.name().empty() && std::regex_match(consumer.name(), _regex) != _invert; } | |||||
| @@ -0,0 +1,43 @@ | |||||
| # Initialize ###################################################################################### | |||||
| Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE ) | |||||
| Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC ) | |||||
| Include ( cmake_tests OPTIONAL RESULT_VARIABLE HAS_CMAKE_TESTS ) | |||||
| # Test ############################################################################################ | |||||
| Find_Package ( GTest ) | |||||
| If ( NOT "${GTest_FOUND}" ) | |||||
| Return ( ) | |||||
| EndIf ( ) | |||||
| File ( GLOB_RECURSE CPPLOGGING_TEST_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h ) | |||||
| File ( GLOB_RECURSE CPPLOGGING_TEST_INLINE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.inl ) | |||||
| File ( GLOB_RECURSE CPPLOGGING_TEST_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ) | |||||
| Add_Executable ( cpplogging-test | |||||
| EXCLUDE_FROM_ALL | |||||
| ${CPPLOGGING_TEST_HEADER_FILES} | |||||
| ${CPPLOGGING_TEST_INLINE_FILES} | |||||
| ${CPPLOGGING_TEST_SOURCE_FILES} ) | |||||
| Target_Link_Libraries ( cpplogging-test | |||||
| PUBLIC | |||||
| cpplogging-objects | |||||
| GMock::Main ) | |||||
| # pedantic | |||||
| If ( HAS_PEDANTIC ) | |||||
| Pedantic_Apply_Flags_Target ( cpplogging-test ALL ) | |||||
| EndIf ( ) | |||||
| # optimization | |||||
| If ( HAS_COTIRE ) | |||||
| Cotire ( cpplogging-test ) | |||||
| EndIf ( ) | |||||
| # test | |||||
| If ( HAS_CMAKE_TESTS ) | |||||
| Add_CMake_Test ( NAME cpplogging TARGET cpplogging-test ) | |||||
| Else ( ) | |||||
| Add_Test ( NAME cpplogging COMMAND cpplogging-test ) | |||||
| EndIf ( ) | |||||
| @@ -0,0 +1,127 @@ | |||||
| #include <gtest/gtest.h> | |||||
| #include <gmock/gmock.h> | |||||
| #include <cpplogging/manager.h> | |||||
| using namespace ::testing; | |||||
| using namespace ::cpplogging; | |||||
| struct LoggingReset | |||||
| { | |||||
| ~LoggingReset() | |||||
| { manager::reset(); } | |||||
| }; | |||||
| struct consumer_mock | |||||
| : public consumer | |||||
| { | |||||
| MOCK_CONST_METHOD1(write_entry, void (const log_entry_ptr_s& entry)); | |||||
| consumer_mock(const std::string& n) : | |||||
| consumer(n, true) | |||||
| { } | |||||
| }; | |||||
| MATCHER_P5(MatchLogData, level, sender, thread, name, message, "") | |||||
| { | |||||
| if (!arg) | |||||
| return false; | |||||
| auto& d = *arg; | |||||
| return d.level == level | |||||
| && d.sender == sender | |||||
| && d.thread == thread | |||||
| && d.name == name | |||||
| && d.message == message; | |||||
| } | |||||
| TEST(LoggingTests, matcher_all) | |||||
| { | |||||
| LoggingReset loggingReset; | |||||
| consumer_stream c0("TestConsumer1", std::cout, false); | |||||
| consumer_stream c1("TestConsumer2", std::cout, false); | |||||
| auto& l0 = logger::get(); | |||||
| auto& l1 = logger::get("TestLogger"); | |||||
| matcher_all matcher; | |||||
| EXPECT_TRUE(matcher.match(c0)); | |||||
| EXPECT_TRUE(matcher.match(c1)); | |||||
| EXPECT_TRUE(matcher.match(l0)); | |||||
| EXPECT_TRUE(matcher.match(l1)); | |||||
| } | |||||
| TEST(LoggingTests, matcher_default) | |||||
| { | |||||
| LoggingReset loggingReset; | |||||
| consumer_stream c0("TestConsumer1", std::cout, false); | |||||
| consumer_stream c1("TestConsumer2", std::cout, false); | |||||
| auto& l0 = logger::get(); | |||||
| auto& l1 = logger::get("TestLogger"); | |||||
| matcher_default_logger matcher; | |||||
| EXPECT_FALSE(matcher.match(c0)); | |||||
| EXPECT_FALSE(matcher.match(c1)); | |||||
| EXPECT_TRUE (matcher.match(l0)); | |||||
| EXPECT_FALSE(matcher.match(l1)); | |||||
| } | |||||
| TEST(LoggingTests, matcher_regex) | |||||
| { | |||||
| LoggingReset loggingReset; | |||||
| consumer_stream c0("TestConsumer1", std::cout, false); | |||||
| consumer_stream c1("TestConsumer2", std::cout, false); | |||||
| auto& l0 = logger::get(); | |||||
| auto& l1 = logger::get("TestLogger"); | |||||
| matcher_regex matcher0("TestConsumer1"); | |||||
| matcher_regex matcher1("ASEF", true); | |||||
| EXPECT_TRUE (matcher0.match(c0)); | |||||
| EXPECT_FALSE(matcher0.match(c1)); | |||||
| EXPECT_FALSE(matcher1.match(l0)); | |||||
| EXPECT_TRUE (matcher1.match(l1)); | |||||
| } | |||||
| TEST(LoggingTests, log_base) | |||||
| { | |||||
| LoggingReset loggingReset; | |||||
| StrictMock<consumer_mock> c0("consumer0"); | |||||
| StrictMock<consumer_mock> c1("Consumer1"); | |||||
| EXPECT_CALL(c0, write_entry(MatchLogData( | |||||
| log_level::info, | |||||
| (void*)0x12, | |||||
| std::this_thread::get_id(), | |||||
| std::string("logger0"), | |||||
| std::string("test1 info")))); | |||||
| EXPECT_CALL(c0, write_entry(MatchLogData( | |||||
| log_level::warn, | |||||
| (void*)0x13, | |||||
| std::this_thread::get_id(), | |||||
| std::string("logger0"), | |||||
| std::string("test1 warn")))); | |||||
| manager::define_rule( | |||||
| std::make_unique<matcher_regex>("logger0"), | |||||
| std::make_unique<matcher_regex>("consumer0"), | |||||
| log_level::info, | |||||
| log_level::warn); | |||||
| auto& l0 = logger::get("logger0"); | |||||
| auto& l1 = logger::get("logger1"); | |||||
| cpplogging_log(l0, debug).sender((void*)0x11).message("test1") << " debug"; | |||||
| cpplogging_log(l0, info ).sender((void*)0x12).message("test1") << " info"; | |||||
| cpplogging_log(l0, warn ).sender((void*)0x13).message("test1") << " warn"; | |||||
| cpplogging_log(l0, error).sender((void*)0x14).message("test1") << " error"; | |||||
| cpplogging_log(l1, debug).sender((void*)0x21).message("test2") << " debug"; | |||||
| cpplogging_log(l1, info ).sender((void*)0x22).message("test2") << " info"; | |||||
| cpplogging_log(l1, warn ).sender((void*)0x23).message("test2") << " warn"; | |||||
| cpplogging_log(l1, error).sender((void*)0x24).message("test2") << " error"; | |||||
| } | |||||