From b45158b7cab5424294a04f841012131f78876fc6 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Fri, 19 Jul 2024 08:49:45 -0600 Subject: [PATCH] :sparkles: Add capability for flavored (e.g. secure) logs --- docs/logging.adoc | 70 +++++++++++++++++++++++++++++++++++++++-- include/log/log.hpp | 54 +++++++++++++++++++++---------- include/sc/format.hpp | 8 ----- test/log/fmt_logger.cpp | 29 +++++++++++++++++ 4 files changed, 134 insertions(+), 27 deletions(-) diff --git a/docs/logging.adoc b/docs/logging.adoc index dbadc135..77299d54 100644 --- a/docs/logging.adoc +++ b/docs/logging.adoc @@ -48,8 +48,7 @@ CIB_ERROR(...); CIB_FATAL(...); ---- -Or `CIB_LOG(level, ...)` can be used equivalently to these where the level is -explicit. `CIB_FATAL` causes a call to +`CIB_FATAL` causes a call to https://intel.github.io/cpp-std-extensions/#_panic_hpp[`stdx::panic`], and `CIB_ASSERT(expression)` is equivalent to `CIB_FATAL` in the case where the expression evaluates to `false`. @@ -132,6 +131,30 @@ template <> inline auto logging::config<> = my_logger_config{}; ---- +=== Flavored logs + +There is not always just one logging backend in an application. For example, you +might want regular logs and secure logs. Providing more backends is possible by specializing +`logging::config` with custom types. + +[source,cpp] +---- +struct secure_tag; + +template <> +inline auto logging::config = my_logger_config{}; +---- + +And this backend can be most easily used by defining macros in terms of the +`CIB_LOG` macro: + +[source,cpp] +---- +#define SECURE_TRACE(...) CIB_LOG(secure_tag, logging::level::TRACE, __VA_ARGS__) +#define SECURE_INFO(...) CIB_LOG(secure_tag, logging::level::INFO, __VA_ARGS__) +// etc +---- + === Modules It can be helpful to scope or filter log messages by associating them with @@ -170,3 +193,46 @@ struct my_struct { On a constrained system, space for text can be at a premium. The `sc` library and the MIPI Sys-T logger combine to xref:sc.adoc#_efficient_logging_with_mipi_sys_t[solve this problem]. + +=== Version logging + +To provide version information in a log, specialize the `version::config` +variable template. The configuration should provide a `build_id` and a +`version_string`. + +[source,cpp] +---- +struct my_version_config { + constexpr static auto build_id = std::uint64_t{1234}; + constexpr static auto version_string = stdx::ct_string{"version"}; +}; + +template <> inline auto version::config<> = my_version_config{}; +---- + +Then use `CIB_LOG_VERSION()` to log the version. If the logging config provides +a `log_build` function, that will be used. Otherwise a text string will be +logged. + +[source,cpp] +---- +struct my_logger_config { + struct { + template auto log_build() -> void { + // log the build version according to my mechanism + } + } logger; +}; +template <> +inline auto logging::config<> = my_logger_config{}; + +CIB_LOG_VERSION(); // calls my_logger_config::log_build +---- + +The easiest way to flavor the version logging is to define a macro in terms of +`CIB_LOG_V`: + +[source,cpp] +---- +#define LOG_SECURE_VERSION(...) CIB_LOG_V(secure_tag) +---- diff --git a/include/log/log.hpp b/include/log/log.hpp index e2755f12..3ba9846c 100644 --- a/include/log/log.hpp +++ b/include/log/log.hpp @@ -37,9 +37,21 @@ concept loggable = requires(T const &t) { t.apply([](StringType, auto const &...) {}); }; -template +struct default_flavor_t; + +template +constexpr static auto get_config() -> auto & { + if constexpr (std::same_as) { + return config; + } else { + return config; + } +} + +template static auto log(TArgs &&...args) -> void { - auto &cfg = config; + auto &cfg = get_config(); cfg.logger.template log(std::forward(args)...); } @@ -66,23 +78,28 @@ using cib_log_module_id_t = typename logging::module_id_t<"default">::type; typename logging::module_id_t::type CIB_PRAGMA_SEMI CIB_PRAGMA( \ diagnostic pop) -#define CIB_LOG(LEVEL, MSG, ...) \ - logging::log( \ - __FILE__, __LINE__, sc::formatter{MSG##_sc}(__VA_ARGS__)) - -#define CIB_TRACE(...) CIB_LOG(logging::level::TRACE, __VA_ARGS__) -#define CIB_INFO(...) CIB_LOG(logging::level::INFO, __VA_ARGS__) -#define CIB_WARN(...) CIB_LOG(logging::level::WARN, __VA_ARGS__) -#define CIB_ERROR(...) CIB_LOG(logging::level::ERROR, __VA_ARGS__) +#define CIB_LOG(FLAVOR, LEVEL, MSG, ...) \ + logging::log( \ + __FILE__, __LINE__, sc::format(MSG##_sc __VA_OPT__(, ) __VA_ARGS__)) + +#define CIB_TRACE(...) \ + CIB_LOG(logging::default_flavor_t, logging::level::TRACE, __VA_ARGS__) +#define CIB_INFO(...) \ + CIB_LOG(logging::default_flavor_t, logging::level::INFO, __VA_ARGS__) +#define CIB_WARN(...) \ + CIB_LOG(logging::default_flavor_t, logging::level::WARN, __VA_ARGS__) +#define CIB_ERROR(...) \ + CIB_LOG(logging::default_flavor_t, logging::level::ERROR, __VA_ARGS__) #define CIB_FATAL(...) \ - (CIB_LOG(logging::level::FATAL, __VA_ARGS__), STDX_PANIC(__VA_ARGS__)) + (CIB_LOG(logging::default_flavor_t, logging::level::FATAL, __VA_ARGS__), \ + STDX_PANIC(__VA_ARGS__)) #define CIB_ASSERT(expr) \ ((expr) ? void(0) : CIB_FATAL("Assertion failure: " #expr)) namespace logging { -template static auto log_version() -> void { - auto &l_cfg = config; +template static auto log_version() -> void { + auto &l_cfg = get_config(); auto &v_cfg = ::version::config; if constexpr (requires { l_cfg.logger.template log_build static auto log_version() -> void { }) { l_cfg.logger.template log_build(); } else { - CIB_LOG(level::MAX, "Version: {} ({})", sc::uint_, - stdx::ct_string_to_type()); + l_cfg.logger.template log( + "", 0, + sc::format("Version: {} ({})"_sc, sc::uint_, + stdx::ct_string_to_type())); } } } // namespace logging -#define CIB_LOG_VERSION() logging::log_version() +#define CIB_LOG_V(FLAVOR) logging::log_version() +#define CIB_LOG_VERSION() CIB_LOG_V(logging::default_flavor_t) diff --git a/include/sc/format.hpp b/include/sc/format.hpp index 0827901e..cc659ba7 100644 --- a/include/sc/format.hpp +++ b/include/sc/format.hpp @@ -100,12 +100,4 @@ constexpr auto format(Fmt, Args... args) { return lazy_string_format{r[0_idx] + r[1_idx], r[2_idx]}; } } - -template struct formatter { - constexpr explicit formatter(T) {} - - template constexpr auto operator()(Ts &&...args) { - return format(T{}, std::forward(args)...); - } -}; } // namespace sc diff --git a/test/log/fmt_logger.cpp b/test/log/fmt_logger.cpp index 38659cd8..714c499c 100644 --- a/test/log/fmt_logger.cpp +++ b/test/log/fmt_logger.cpp @@ -150,3 +150,32 @@ TEST_CASE("log version", "[fmt_logger]") { CHECK(buffer.find("MAX [default]: Version: 1234 (test version)") != std::string::npos); } + +namespace { +struct secure_t; +std::string secure_buffer{}; +} // namespace + +template <> +inline auto logging::config = + logging::fmt::config{std::back_inserter(secure_buffer)}; + +TEST_CASE("logging can be flavored", "[fmt_logger]") { + buffer.clear(); + secure_buffer.clear(); + CIB_LOG(secure_t, logging::level::TRACE, "Hello"); + CAPTURE(secure_buffer); + CHECK(secure_buffer.substr(secure_buffer.size() - std::size("Hello")) == + "Hello\n"); + CHECK(buffer.empty()); +} + +TEST_CASE("log version can be flavored", "[fmt_logger]") { + buffer.clear(); + secure_buffer.clear(); + CIB_LOG_V(secure_t); + CAPTURE(secure_buffer); + CHECK(secure_buffer.find("MAX [default]: Version: 1234 (test version)") != + std::string::npos); + CHECK(buffer.empty()); +}