| @@ -0,0 +1 @@ | |||
| build/ | |||
| @@ -0,0 +1,3 @@ | |||
| #pragma once | |||
| #cmakedefine CPPLOGGING_HAS_NLOHMANN_JSON | |||
| @@ -21,11 +21,13 @@ namespace cpplogging | |||
| std::string _name; //!< name of the consumer | |||
| format_type _format; //!< parsed logging format | |||
| public: | |||
| /** | |||
| * @brief Get the default log format. | |||
| */ | |||
| static inline const std::string& default_format(); | |||
| protected: | |||
| /** | |||
| * @brief Parse the given format and generate a pre-compiled list of strings. | |||
| * | |||
| @@ -49,13 +51,11 @@ namespace cpplogging | |||
| * @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()); | |||
| const std::string& name, | |||
| const std::string& format = default_format()); | |||
| /** | |||
| * @breif Destructor. | |||
| @@ -25,20 +25,26 @@ namespace cpplogging | |||
| /** | |||
| * @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. | |||
| * @param[in] name Name of the consumer. | |||
| * @param[in] stream Stream to write data to. | |||
| * @param[in] format Format to use for logging. | |||
| */ | |||
| consumer_stream(const std::string& name, std::ostream& stream, bool auto_register); | |||
| consumer_stream( | |||
| const std::string& name, | |||
| std::ostream& stream, | |||
| const std::string& format = default_format()); | |||
| /** | |||
| * @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. | |||
| * @param[in] name Name of the consumer. | |||
| * @param[in] stream Stream to write data to. | |||
| * @param[in] format Format to use for logging. | |||
| */ | |||
| consumer_stream(const std::string& name, stream_ptr_u&& stream, bool auto_register); | |||
| consumer_stream( | |||
| const std::string& name, | |||
| stream_ptr_u&& stream, | |||
| const std::string& format = default_format()); | |||
| /** | |||
| * @brief Consume a log entry. | |||
| @@ -3,11 +3,18 @@ | |||
| #include <set> | |||
| #include <map> | |||
| #include <list> | |||
| #include <string> | |||
| #include <cpplogging/config.h> | |||
| #include "rule.h" | |||
| #include "logger_impl.h" | |||
| #include "consumer/consumer.h" | |||
| #ifdef CPPLOGGING_HAS_NLOHMANN_JSON | |||
| #define CPPLOGGING_HAS_LOAD_CONFIG | |||
| #endif | |||
| namespace cpplogging | |||
| { | |||
| @@ -148,6 +155,15 @@ namespace cpplogging | |||
| */ | |||
| static void reset(); | |||
| #ifdef CPPLOGGING_HAS_LOAD_CONFIG | |||
| /** | |||
| * @brief Load current configuration from file. | |||
| * | |||
| * @param[in] filename Filename to load configuration from. | |||
| */ | |||
| static void load(const std::string& filename); | |||
| #endif | |||
| private: | |||
| /** | |||
| * @brief Initialize the given logger instance. | |||
| @@ -20,10 +20,18 @@ Option ( CPPLOGGING_NO_STRIP | |||
| "Do not strip debug symbols from binary." | |||
| OFF ) | |||
| Find_Package ( nlohmann_json ) | |||
| If ( nlohmann_json_FOUND ) | |||
| Set ( CPPLOGGING_HAS_NLOHMANN_JSON true ) | |||
| EndIf ( ) | |||
| # Object Library ################################################################################## | |||
| Set ( CMAKE_POSITION_INDEPENDENT_CODE ON ) | |||
| Set ( CPPLOGGING_GENERATED_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/include ) | |||
| Set ( CPPLOGGING_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include ) | |||
| Configure_File ( ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/config.h.in | |||
| ${CPPLOGGING_GENERATED_INCLUDE_DIR}/cpplogging/config.h ) | |||
| 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 ) | |||
| @@ -32,9 +40,15 @@ Add_Library ( cpplogging-objects | |||
| ${CPPLOGGING_HEADER_FILES} | |||
| ${CPPLOGGING_INLINE_FILES} | |||
| ${CPPLOGGING_SOURCE_FILES} ) | |||
| If ( CPPLOGGING_HAS_NLOHMANN_JSON ) | |||
| Target_Link_Libraries ( cpplogging-objects | |||
| PRIVATE | |||
| nlohmann_json::nlohmann_json ) | |||
| EndIf ( ) | |||
| Target_Include_Directories ( cpplogging-objects | |||
| PUBLIC | |||
| $<BUILD_INTERFACE:${CPPLOGGING_INCLUDE_DIR}> | |||
| $<BUILD_INTERFACE:${CPPLOGGING_GENERATED_INCLUDE_DIR}> | |||
| $<INSTALL_INTERFACE:${CPPLOGGING_INSTALL_DIR_INCLUDE}> ) | |||
| # Static Library ################################################################################## | |||
| @@ -82,7 +96,7 @@ EndIf ( ) | |||
| # Header | |||
| If ( CPPLOGGING_INSTALL_HEADER ) | |||
| Install ( FILES ${CPPLOGGING_INCLUDE_DIR}/cpplogging.h | |||
| Install ( DIRECTORY ${CPPLOGGING_GENERATED_INCLUDE_DIR}/cpplogging | |||
| DESTINATION ${CPPLOGGING_INSTALL_DIR_INCLUDE} ) | |||
| Install ( DIRECTORY ${CPPLOGGING_INCLUDE_DIR}/cpplogging | |||
| DESTINATION ${CPPLOGGING_INSTALL_DIR_INCLUDE} ) | |||
| @@ -3,13 +3,10 @@ | |||
| using namespace ::cpplogging; | |||
| consumer::consumer(const std::string& name, bool auto_register, const std::string& format) | |||
| consumer::consumer(const std::string& name, const std::string& format) | |||
| : _name (name) | |||
| , _format (parse_format(format)) | |||
| { | |||
| if (auto_register) | |||
| manager::register_consumer(*this); | |||
| } | |||
| { } | |||
| consumer::~consumer() | |||
| { | |||
| @@ -6,14 +6,20 @@ | |||
| using namespace ::cpplogging; | |||
| consumer_stream::consumer_stream(const std::string& name, std::ostream& stream, bool auto_register) | |||
| : consumer (name, auto_register) | |||
| consumer_stream::consumer_stream( | |||
| const std::string& name, | |||
| std::ostream& stream, | |||
| const std::string& format) | |||
| : consumer (name, format) | |||
| , _stream (stream) | |||
| , _storage (nullptr) | |||
| { } | |||
| consumer_stream::consumer_stream(const std::string& name, stream_ptr_u&& stream, bool auto_register) | |||
| : consumer (name, auto_register) | |||
| consumer_stream::consumer_stream( | |||
| const std::string& name, | |||
| stream_ptr_u&& stream, | |||
| const std::string& format) | |||
| : consumer (name, format) | |||
| , _stream (*stream) | |||
| , _storage (std::move(stream)) | |||
| { } | |||
| @@ -1,5 +1,15 @@ | |||
| #include <fstream> | |||
| #include <cpplogging/manager/manager.h> | |||
| #include <cpplogging/manager/manager.inl> | |||
| #include <cpplogging/manager/matcher/matcher_all.h> | |||
| #include <cpplogging/manager/matcher/matcher_regex.h> | |||
| #include <cpplogging/manager/matcher/matcher_default_logger.h> | |||
| #include <cpplogging/manager/consumer/consumer_stream.h> | |||
| #ifdef CPPLOGGING_HAS_NLOHMANN_JSON | |||
| #include <nlohmann/json.hpp> | |||
| #endif | |||
| using namespace ::cpplogging; | |||
| @@ -129,6 +139,181 @@ void manager::reset() | |||
| _consumer.clear(); | |||
| } | |||
| #ifdef CPPLOGGING_HAS_LOAD_CONFIG | |||
| matcher_ptr_u load_matcher(const ::nlohmann::json& jMatchers, const ::nlohmann::json& jName) | |||
| { | |||
| if (!jName.is_string()) | |||
| throw std::invalid_argument("expected matcher value to be an string"); | |||
| auto& jMatcher = jMatchers[jName.get<std::string>()]; | |||
| if (!jMatcher.is_object()) | |||
| throw std::invalid_argument("expected matcher to be an object"); | |||
| auto& jType = jMatcher["type"]; | |||
| if (!jType.is_string()) | |||
| throw std::invalid_argument("expected 'matcher.type' to be an string"); | |||
| /* match all cosumers/loggers */ | |||
| auto type = jType.get<std::string>(); | |||
| if (type == "all") | |||
| { | |||
| return std::make_unique<matcher_all>(); | |||
| } | |||
| /* match the default logger */ | |||
| else if (type == "default_logger") | |||
| { | |||
| return std::make_unique<matcher_default_logger>(); | |||
| } | |||
| /* regex */ | |||
| else if (type == "regex") | |||
| { | |||
| /* read regex value */ | |||
| auto& jValue = jMatcher["value"]; | |||
| if (!jValue.is_string()) | |||
| throw std::invalid_argument("expected 'matcher.value' to be an string"); | |||
| auto regex = jValue.get<std::string>(); | |||
| /* read regex invert */ | |||
| bool invert = false; | |||
| auto& jInvert = jMatcher["invert"]; | |||
| if (!jValue.empty() && !jValue.is_boolean()) | |||
| throw std::invalid_argument("expected 'matcher.invert' to be an bool"); | |||
| invert = jInvert.get<bool>(); | |||
| return std::make_unique<matcher_regex>(regex, invert); | |||
| } | |||
| /* invalid type */ | |||
| else | |||
| { | |||
| using namespace ::std; | |||
| throw std::invalid_argument("unknown matcher type: "s + type); | |||
| } | |||
| } | |||
| log_level load_log_level(const ::nlohmann::json& jLogLevel, log_level default_level) | |||
| { | |||
| if (jLogLevel.empty()) | |||
| return default_level; | |||
| if (!jLogLevel.is_string()) | |||
| throw std::invalid_argument("expected rule log level to be an string"); | |||
| auto tmp = jLogLevel.get<std::string>(); | |||
| if (tmp == "debug") | |||
| return log_level::debug; | |||
| else if (tmp == "info") | |||
| return log_level::info; | |||
| else if (tmp == "warn") | |||
| return log_level::warn; | |||
| else if (tmp == "error") | |||
| return log_level::error; | |||
| else | |||
| { | |||
| using namespace ::std; | |||
| throw std::invalid_argument("invalid log level: "s + tmp); | |||
| } | |||
| } | |||
| void manager::load(const std::string& filename) | |||
| { | |||
| using namespace ::nlohmann; | |||
| /* load the json object */ | |||
| std::ifstream ifs(filename); | |||
| json root; | |||
| ifs >> root; | |||
| /* get root nodes */ | |||
| auto rules = root["rules"]; | |||
| auto matchers = root["matchers"]; | |||
| auto consumers = root["consumers"]; | |||
| /* check root nodes */ | |||
| if (rules.empty() || !rules.is_array()) | |||
| throw std::invalid_argument("expected 'rules' to be an json array"); | |||
| if (matchers.empty() || !matchers.is_object()) | |||
| throw std::invalid_argument("expected 'matchers' to be an json object"); | |||
| if (consumers.empty() || !consumers.is_object()) | |||
| throw std::invalid_argument("expected 'consumers' to be an json object"); | |||
| /* read consumers */ | |||
| for (auto& c : consumers.items()) | |||
| { | |||
| auto name = c.key(); | |||
| auto obj = c.value(); | |||
| if (!obj.is_object()) | |||
| throw std::invalid_argument("expected 'consumer' to be an json object"); | |||
| /* type */ | |||
| auto& jType = obj["type"]; | |||
| if (!jType.is_string()) | |||
| throw std::invalid_argument("expected 'consumer.type' to be an string"); | |||
| /* format */ | |||
| auto& jFormat = obj["format"]; | |||
| auto format = consumer::default_format(); | |||
| if (jFormat.empty()) | |||
| { | |||
| if (!jFormat.is_string()) | |||
| throw std::invalid_argument("expected 'consumer.format' to be an string"); | |||
| format = jFormat.get<std::string>(); | |||
| } | |||
| /* log to file */ | |||
| auto type = jType.get<std::string>(); | |||
| if (type == "file") | |||
| { | |||
| auto& jFilename = obj["filename"]; | |||
| if (!jFilename.is_string()) | |||
| throw std::invalid_argument("expected 'consumer.filename' to be an string"); | |||
| auto fn = jFilename.get<std::string>(); | |||
| register_consumer(std::make_unique<consumer_stream>( | |||
| name, | |||
| std::make_unique<std::ofstream>(fn), | |||
| format)); | |||
| } | |||
| /* log to stdout */ | |||
| else if (type == "stdout") | |||
| { | |||
| register_consumer(std::make_unique<consumer_stream>( | |||
| name, | |||
| std::cout, | |||
| format)); | |||
| } | |||
| /* log to stderr */ | |||
| else if (type == "stderr") | |||
| { | |||
| register_consumer(std::make_unique<consumer_stream>( | |||
| name, | |||
| std::cerr, | |||
| format)); | |||
| } | |||
| /* invalid consumer type */ | |||
| else | |||
| { | |||
| using namespace ::std; | |||
| throw std::invalid_argument("unknown consumer type: "s + type); | |||
| } | |||
| } | |||
| /* read rules */ | |||
| for (auto& r : rules) | |||
| { | |||
| define_rule( | |||
| load_matcher(matchers, r["logger"]), | |||
| load_matcher(matchers, r["consumer"]), | |||
| load_log_level(r["min"], log_level::debug), | |||
| load_log_level(r["max"], log_level::error)); | |||
| } | |||
| } | |||
| #endif | |||
| logger_impl& manager::init_logger(logger_impl& logger) | |||
| { | |||
| for (auto& rule : _rules) | |||
| @@ -17,8 +17,10 @@ struct consumer_mock | |||
| MOCK_CONST_METHOD1(write_entry, void (const log_entry_ptr_s& entry)); | |||
| consumer_mock(const std::string& n) : | |||
| consumer(n, true) | |||
| { } | |||
| consumer(n) | |||
| { | |||
| manager::register_consumer(*this); | |||
| } | |||
| }; | |||
| MATCHER_P5(MatchLogData, level, sender, thread, name, message, "") | |||
| @@ -37,8 +39,8 @@ MATCHER_P5(MatchLogData, level, sender, thread, name, message, "") | |||
| TEST(LoggingTests, matcher_all) | |||
| { | |||
| LoggingReset loggingReset; | |||
| consumer_stream c0("TestConsumer1", std::cout, false); | |||
| consumer_stream c1("TestConsumer2", std::cout, false); | |||
| consumer_stream c0("TestConsumer1", std::cout); | |||
| consumer_stream c1("TestConsumer2", std::cout); | |||
| auto& l0 = logger::get(); | |||
| auto& l1 = logger::get("TestLogger"); | |||
| @@ -54,8 +56,8 @@ TEST(LoggingTests, matcher_all) | |||
| TEST(LoggingTests, matcher_default) | |||
| { | |||
| LoggingReset loggingReset; | |||
| consumer_stream c0("TestConsumer1", std::cout, false); | |||
| consumer_stream c1("TestConsumer2", std::cout, false); | |||
| consumer_stream c0("TestConsumer1", std::cout); | |||
| consumer_stream c1("TestConsumer2", std::cout); | |||
| auto& l0 = logger::get(); | |||
| auto& l1 = logger::get("TestLogger"); | |||
| @@ -71,8 +73,8 @@ TEST(LoggingTests, matcher_default) | |||
| TEST(LoggingTests, matcher_regex) | |||
| { | |||
| LoggingReset loggingReset; | |||
| consumer_stream c0("TestConsumer1", std::cout, false); | |||
| consumer_stream c1("TestConsumer2", std::cout, false); | |||
| consumer_stream c0("TestConsumer1", std::cout); | |||
| consumer_stream c1("TestConsumer2", std::cout); | |||
| auto& l0 = logger::get(); | |||
| auto& l1 = logger::get("TestLogger"); | |||
| @@ -125,3 +127,10 @@ TEST(LoggingTests, log_base) | |||
| cpplogging_log(l1, warn ).sender((void*)0x23).message("test2") << " warn"; | |||
| cpplogging_log(l1, error).sender((void*)0x24).message("test2") << " error"; | |||
| } | |||
| #ifdef CPPLOGGING_HAS_LOAD_CONFIG | |||
| TEST(LoggingTests, load) | |||
| { | |||
| manager::load("./cpplogging/test_config.json"); | |||
| } | |||
| #endif | |||
| @@ -0,0 +1,36 @@ | |||
| { | |||
| "consumers": { | |||
| "c_stdout": { | |||
| "type": "stdout", | |||
| "format": "[${runtime:-016.6f}] ${message}" | |||
| }, | |||
| "c_stderr": { | |||
| "type": "stderr", | |||
| "format": "[${runtime:-016.6f}] ${message}" | |||
| }, | |||
| "c_file": { | |||
| "type": "file", | |||
| "filename": "test_file.log", | |||
| "format": "[${runtime:-016.6f}] ${message}" | |||
| } | |||
| }, | |||
| "matchers": { | |||
| "m_all": { | |||
| "type": "all" | |||
| }, | |||
| "m_default_logger": { | |||
| "type": "default_logger" | |||
| }, | |||
| "m_regex": { | |||
| "type": "regex", | |||
| "value": "abc.*?xyz", | |||
| "invert": true | |||
| } | |||
| }, | |||
| "rules": [{ | |||
| "consumer": "m_all", | |||
| "logger": "m_default_logger", | |||
| "min": "info", | |||
| "max": "error" | |||
| }] | |||
| } | |||