* 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"; | |||
| } | |||