diff --git a/CMakeLists.txt b/CMakeLists.txt index 445e0546..499dc8dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ target_sources( include FILES include/log/env.hpp + include/log/flavor.hpp include/log/level.hpp include/log/log.hpp include/log/module.hpp) diff --git a/docs/logging.adoc b/docs/logging.adoc index 77299d54..afa24145 100644 --- a/docs/logging.adoc +++ b/docs/logging.adoc @@ -86,8 +86,7 @@ implementation is a matter of defining this structure appropriately. ---- struct my_logger_config { struct { - template + template auto log(File, Line, Msg const &msg) -> void { // log according to my mechanism } @@ -95,9 +94,8 @@ struct my_logger_config { }; ---- -Notice that the first two template parameters to log are the -xref:logging.adoc#_log_levels[level] and the xref:logging.adoc#_modules[module -ID]. The `ModuleId` type is a xref:sc.adoc#_string_constants[compile-time string]. +Notice that the first template parameters to log is the +xref:logging.adoc#_logging_environments[environment]. The first two runtime parameters receive preprocessor `\_​_FILE_​\_` and `__LINE_​_` values respectively. The `msg` argument is a structure containing a @@ -109,8 +107,7 @@ way to implement `log` is: ---- struct my_logger_config { struct { - template + template auto log(File, Line, Msg const &msg) -> void { msg.apply([] (Str, auto const&... args) { std::print(Str::value, args...); @@ -236,3 +233,47 @@ The easiest way to flavor the version logging is to define a macro in terms of ---- #define LOG_SECURE_VERSION(...) CIB_LOG_V(secure_tag) ---- + +=== Logging environments + +The logging environment is a compile-time map from types to values that allows a +logger to look up various parameters, including the module ID, the log level, +and the flavor. It can also be used to provide user-defined values to be +interpreted by a logging backend. + +The macros that implement logging with various levels, modules, and flavors +are implemented as environment declarations, for example: +[source,cpp] +---- +CIB_LOG_ENV(logging::get_level, logging::level::TRACE); +CIB_LOG("Hello"); // logs with TRACE level +---- +or: +[source,cpp] +---- +CIB_LOG_ENV(logging::get_flavor, secure_tag); +CIB_TRACE("Hello"); // logs with secure back end +---- +A temporary override of values can be done with `CIB_WITH_LOG_ENV`: +[source,cpp] +---- +CIB_WITH LOG_ENV(logging::get_level, logging::level::TRACE, + logging::get_flavor, secure_tag) { + CIB_LOG("Hello"); // logs a TRACE with secure back end +} +---- + +To interrogate the environment from a custom logger, use the appropriate query +on the environment. +[source,cpp] +---- +struct my_logger_config { + struct { + template + auto log(File, Line, Msg const &msg) -> void { + constexpr auto level = get_level(Env{}).value; + // ... + } + } logger; +}; +---- diff --git a/include/flow/impl.hpp b/include/flow/impl.hpp index 15d9827e..875ecef1 100644 --- a/include/flow/impl.hpp +++ b/include/flow/impl.hpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -22,9 +24,11 @@ constexpr auto run_func() -> void { if constexpr (not FlowName.empty()) { using log_spec_t = decltype(get_log_spec>()); - CIB_LOG_ENV(logging::get_level, log_spec_t::level); - CIB_LOG(typename log_spec_t::flavor, "flow.{}({})", - typename CTNode::type_t{}, typename CTNode::name_t{}); + CIB_LOG_ENV(logging::get_level, log_spec_t::level, + logging::get_flavor, + stdx::type_identity{}); + CIB_LOG("flow.{}({})", typename CTNode::type_t{}, + typename CTNode::name_t{}); } typename CTNode::func_t{}(); } @@ -62,16 +66,20 @@ template struct inlined_func_list { if constexpr (loggingEnabled) { using log_spec_t = decltype(get_log_spec()); - CIB_LOG_ENV(logging::get_level, log_spec_t::level); - CIB_LOG(typename log_spec_t::flavor, "flow.start({})", name); + CIB_LOG_ENV(logging::get_level, log_spec_t::level, + logging::get_flavor, + stdx::type_identity{}); + CIB_LOG("flow.start({})", name); } (FuncPtrs(), ...); if constexpr (loggingEnabled) { using log_spec_t = decltype(get_log_spec()); - CIB_LOG_ENV(logging::get_level, log_spec_t::level); - CIB_LOG(typename log_spec_t::flavor, "flow.end({})", name); + CIB_LOG_ENV(logging::get_level, log_spec_t::level, + logging::get_flavor, + stdx::type_identity{}); + CIB_LOG("flow.end({})", name); } } }; diff --git a/include/log/env.hpp b/include/log/env.hpp index 822ed0da..a445ce14 100644 --- a/include/log/env.hpp +++ b/include/log/env.hpp @@ -77,8 +77,11 @@ constexpr auto make_env = [] { return boost::mp11::mp_append{}; }; +template +using extend_env_t = decltype(make_env.template operator()()); + template -using make_env_t = decltype(make_env<>.template operator()()); +using make_env_t = extend_env_t, Args...>; } // namespace logging using cib_log_env_t = logging::env<>; @@ -92,13 +95,9 @@ using cib_log_env_t = logging::env<>; #endif #define CIB_LOG_ENV_DECL(...) \ - [[maybe_unused]] typedef decltype([] { \ - using new_env_t = \ - typename logging::detail::for_each_pair>::template type<_env_args...>; \ - return boost::mp11::mp_append{}; \ - }.template operator()<__VA_ARGS__>()) cib_log_env_t + [[maybe_unused]] typedef decltype([] { \ + return logging::extend_env_t{}; \ + }()) cib_log_env_t #define CIB_LOG_ENV(...) \ STDX_PRAGMA(diagnostic push) \ diff --git a/include/log/flavor.hpp b/include/log/flavor.hpp new file mode 100644 index 00000000..a8126248 --- /dev/null +++ b/include/log/flavor.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include + +namespace logging { +struct default_flavor_t; + +[[maybe_unused]] constexpr inline struct get_flavor_t { + template + requires true + CONSTEVAL auto operator()(T &&t) const noexcept( + noexcept(std::forward(t).query(std::declval()))) + -> decltype(std::forward(t).query(*this)) { + return std::forward(t).query(*this); + } + + CONSTEVAL auto operator()(auto &&) const { + return stdx::ct{}>(); + } +} get_flavor; +} // namespace logging diff --git a/include/log/level.hpp b/include/log/level.hpp index 8552918f..42bc632c 100644 --- a/include/log/level.hpp +++ b/include/log/level.hpp @@ -2,6 +2,7 @@ #include +#include #include #include diff --git a/include/log/log.hpp b/include/log/log.hpp index 1a0a6075..2078c7cf 100644 --- a/include/log/log.hpp +++ b/include/log/log.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -9,6 +10,8 @@ #include #include #include +#include +#include #include #include @@ -35,35 +38,33 @@ struct config { template inline auto config = null::config{}; -struct default_flavor_t; - -template +template ALWAYS_INLINE constexpr static auto get_config() -> auto & { - if constexpr (std::same_as) { + using flavor_t = typename decltype(get_flavor(Env{}).value)::type; + if constexpr (std::same_as) { return config; } else { - return config; + return config; } } -template +template ALWAYS_INLINE static auto log(TArgs &&...args) -> void { - auto &cfg = get_config(); + auto &cfg = get_config(); cfg.logger.template log(std::forward(args)...); } } // namespace logging // NOLINTBEGIN(cppcoreguidelines-macro-usage) -#define CIB_LOG(FLAVOR, MSG, ...) \ - logging::log( \ +#define CIB_LOG(MSG, ...) \ + logging::log( \ __FILE__, __LINE__, sc::format(MSG##_sc __VA_OPT__(, ) __VA_ARGS__)) -#define CIB_LOG_WITH_LEVEL(LEVEL, ...) \ - do { \ - CIB_LOG_ENV(logging::get_level, LEVEL); \ - CIB_LOG(logging::default_flavor_t __VA_OPT__(, ) __VA_ARGS__); \ - } while (false) +#define CIB_LOG_WITH_LEVEL(LEVEL, MSG, ...) \ + logging::log< \ + logging::extend_env_t>( \ + __FILE__, __LINE__, sc::format(MSG##_sc __VA_OPT__(, ) __VA_ARGS__)) #define CIB_TRACE(...) \ CIB_LOG_WITH_LEVEL(logging::level::TRACE __VA_OPT__(, ) __VA_ARGS__) @@ -77,8 +78,7 @@ ALWAYS_INLINE static auto log(TArgs &&...args) -> void { #define CIB_FATAL(MSG, ...) \ [](auto &&str) { \ CIB_LOG_ENV(logging::get_level, logging::level::FATAL); \ - logging::log(__FILE__, \ - __LINE__, str); \ + logging::log(__FILE__, __LINE__, str); \ FWD(str).apply([](S s, Args... args) { \ constexpr auto cts = stdx::ct_string_from_type(s); \ stdx::panic(args...); \ @@ -89,9 +89,9 @@ ALWAYS_INLINE static auto log(TArgs &&...args) -> void { ((expr) ? void(0) : CIB_FATAL("Assertion failure: " #expr)) namespace logging { -template +template ALWAYS_INLINE static auto log_version() -> void { - auto &l_cfg = get_config(); + auto &l_cfg = get_config(); auto &v_cfg = ::version::config; if constexpr (requires { l_cfg.logger.template log_build void { } } // namespace logging -#define CIB_LOG_V(FLAVOR) logging::log_version() +#define CIB_LOG_V(FLAVOR) \ + logging::log_version{}>>() #define CIB_LOG_VERSION() CIB_LOG_V(logging::default_flavor_t) // NOLINTEND(cppcoreguidelines-macro-usage) diff --git a/test/log/fmt_logger.cpp b/test/log/fmt_logger.cpp index 428b8258..723dc1d2 100644 --- a/test/log/fmt_logger.cpp +++ b/test/log/fmt_logger.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -161,17 +162,24 @@ template <> inline auto logging::config = logging::fmt::config{std::back_inserter(secure_buffer)}; +#define SECURE_TRACE(MSG, ...) \ + logging::log{}>>( \ + __FILE__, __LINE__, sc::format(MSG##_sc __VA_OPT__(, ) __VA_ARGS__)) + TEST_CASE("logging can be flavored", "[fmt_logger]") { - CIB_LOG_ENV(logging::get_level, logging::level::TRACE); buffer.clear(); secure_buffer.clear(); - CIB_LOG(secure_t, "Hello"); + SECURE_TRACE("Hello"); CAPTURE(secure_buffer); CHECK(secure_buffer.substr(secure_buffer.size() - std::size("Hello")) == "Hello\n"); CHECK(buffer.empty()); } +#undef SECURE_TRACE + TEST_CASE("log version can be flavored", "[fmt_logger]") { buffer.clear(); secure_buffer.clear(); diff --git a/test/log/level.cpp b/test/log/level.cpp index b29ada8f..af18196f 100644 --- a/test/log/level.cpp +++ b/test/log/level.cpp @@ -39,7 +39,7 @@ struct fmt::formatter> { TEST_CASE("fmt logger works with custom level", "[level]") { CIB_LOG_ENV(logging::get_level, custom_level::THE_ONE_LEVEL); buffer.clear(); - CIB_LOG(logging::default_flavor_t, "Hello"); + CIB_LOG("Hello"); CAPTURE(buffer); CHECK(buffer.find("THE_ONE_LEVEL [default]:") != std::string::npos); }