diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/cmake/config.h.in b/cmake/config.h.in new file mode 100644 index 0000000..2770b9c --- /dev/null +++ b/cmake/config.h.in @@ -0,0 +1,3 @@ +#pragma once + +#cmakedefine CPPLOGGING_HAS_NLOHMANN_JSON diff --git a/include/cpplogging/manager/consumer/consumer.h b/include/cpplogging/manager/consumer/consumer.h index b0e454b..2a56506 100644 --- a/include/cpplogging/manager/consumer/consumer.h +++ b/include/cpplogging/manager/consumer/consumer.h @@ -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. diff --git a/include/cpplogging/manager/consumer/consumer_stream.h b/include/cpplogging/manager/consumer/consumer_stream.h index 8e42940..59ece99 100644 --- a/include/cpplogging/manager/consumer/consumer_stream.h +++ b/include/cpplogging/manager/consumer/consumer_stream.h @@ -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. diff --git a/include/cpplogging/manager/manager.h b/include/cpplogging/manager/manager.h index f6aa60f..fc2b632 100644 --- a/include/cpplogging/manager/manager.h +++ b/include/cpplogging/manager/manager.h @@ -3,11 +3,18 @@ #include #include #include +#include + +#include #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. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 27ace49..76b4e6c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 $ + $ $ ) # 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} ) diff --git a/src/cpplogging/manager/consumer/consumer.cpp b/src/cpplogging/manager/consumer/consumer.cpp index 72073d1..f281b61 100644 --- a/src/cpplogging/manager/consumer/consumer.cpp +++ b/src/cpplogging/manager/consumer/consumer.cpp @@ -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() { diff --git a/src/cpplogging/manager/consumer/consumer_stream.cpp b/src/cpplogging/manager/consumer/consumer_stream.cpp index fea8c9f..6eced68 100644 --- a/src/cpplogging/manager/consumer/consumer_stream.cpp +++ b/src/cpplogging/manager/consumer/consumer_stream.cpp @@ -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)) { } diff --git a/src/cpplogging/manager/manager.cpp b/src/cpplogging/manager/manager.cpp index 2be836c..dea2d4b 100644 --- a/src/cpplogging/manager/manager.cpp +++ b/src/cpplogging/manager/manager.cpp @@ -1,5 +1,15 @@ +#include + #include #include +#include +#include +#include +#include + +#ifdef CPPLOGGING_HAS_NLOHMANN_JSON + #include +#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()]; + 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(); + if (type == "all") + { + return std::make_unique(); + } + + /* match the default logger */ + else if (type == "default_logger") + { + return std::make_unique(); + } + + /* 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(); + + /* 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(); + + return std::make_unique(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(); + 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(); + } + + /* log to file */ + auto type = jType.get(); + 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(); + register_consumer(std::make_unique( + name, + std::make_unique(fn), + format)); + } + + /* log to stdout */ + else if (type == "stdout") + { + register_consumer(std::make_unique( + name, + std::cout, + format)); + } + + /* log to stderr */ + else if (type == "stderr") + { + register_consumer(std::make_unique( + 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) diff --git a/test/cpplogging/cpplogging_tests.cpp b/test/cpplogging/cpplogging_tests.cpp index b35c37a..56a027a 100644 --- a/test/cpplogging/cpplogging_tests.cpp +++ b/test/cpplogging/cpplogging_tests.cpp @@ -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 diff --git a/test/cpplogging/test_config.json b/test/cpplogging/test_config.json new file mode 100644 index 0000000..d63d999 --- /dev/null +++ b/test/cpplogging/test_config.json @@ -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" + }] +} diff --git a/test/test_file.log b/test/test_file.log new file mode 100644 index 0000000..e69de29