Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 68 additions & 2 deletions docs/logging.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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<secure_tag> = 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
Expand Down Expand Up @@ -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 Version, stdx::ct_string S = ""> 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)
----
54 changes: 37 additions & 17 deletions include/log/log.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,21 @@ concept loggable = requires(T const &t) {
t.apply([]<typename StringType>(StringType, auto const &...) {});
};

template <level L, typename ModuleId, typename... Ts, typename... TArgs>
struct default_flavor_t;

template <typename Flavor, typename... Ts>
constexpr static auto get_config() -> auto & {
if constexpr (std::same_as<Flavor, default_flavor_t>) {
return config<Ts...>;
} else {
return config<Flavor, Ts...>;
}
}

template <typename Flavor, level L, typename ModuleId, typename... Ts,
typename... TArgs>
static auto log(TArgs &&...args) -> void {
auto &cfg = config<Ts...>;
auto &cfg = get_config<Flavor, Ts...>();
cfg.logger.template log<L, ModuleId>(std::forward<TArgs>(args)...);
}

Expand All @@ -66,35 +78,43 @@ using cib_log_module_id_t = typename logging::module_id_t<"default">::type;
typename logging::module_id_t<S>::type CIB_PRAGMA_SEMI CIB_PRAGMA( \
diagnostic pop)

#define CIB_LOG(LEVEL, MSG, ...) \
logging::log<LEVEL, cib_log_module_id_t>( \
__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<FLAVOR, LEVEL, cib_log_module_id_t>( \
__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 <typename... Ts> static auto log_version() -> void {
auto &l_cfg = config<Ts...>;
template <typename Flavor, typename... Ts> static auto log_version() -> void {
auto &l_cfg = get_config<Flavor, Ts...>();
auto &v_cfg = ::version::config<Ts...>;
if constexpr (requires {
l_cfg.logger.template log_build<v_cfg.build_id,
v_cfg.version_string>();
}) {
l_cfg.logger.template log_build<v_cfg.build_id, v_cfg.version_string>();
} else {
CIB_LOG(level::MAX, "Version: {} ({})", sc::uint_<v_cfg.build_id>,
stdx::ct_string_to_type<v_cfg.version_string,
sc::string_constant>());
l_cfg.logger.template log<level::MAX, cib_log_module_id_t>(
"", 0,
sc::format("Version: {} ({})"_sc, sc::uint_<v_cfg.build_id>,
stdx::ct_string_to_type<v_cfg.version_string,
sc::string_constant>()));
}
}
} // namespace logging

#define CIB_LOG_VERSION() logging::log_version()
#define CIB_LOG_V(FLAVOR) logging::log_version<FLAVOR>()
#define CIB_LOG_VERSION() CIB_LOG_V(logging::default_flavor_t)
8 changes: 0 additions & 8 deletions include/sc/format.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,4 @@ constexpr auto format(Fmt, Args... args) {
return lazy_string_format{r[0_idx] + r[1_idx], r[2_idx]};
}
}

template <typename T> struct formatter {
constexpr explicit formatter(T) {}

template <typename... Ts> constexpr auto operator()(Ts &&...args) {
return format(T{}, std::forward<Ts>(args)...);
}
};
} // namespace sc
29 changes: 29 additions & 0 deletions test/log/fmt_logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<secure_t> =
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());
}