add Chapter18

This commit is contained in:
Amar Mahmutbegovic
2025-02-06 00:19:59 +01:00
parent 9cc9cc7d73
commit 8634accda5
1533 changed files with 1092521 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
# logging
Logging in *cib* is in two parts:
- the interface, in [log.hpp](log.hpp)
- an implementation, which can be specified at the top level
Three possible logger implementations are provided:
- one using libfmt in [fmt/logger.hpp](fmt/logger.hpp)
- one using the [MIPI SyS-T spec](https://www.mipi.org/specifications/sys-t), in [catalog/mipi_encoder.hpp](catalog/mipi_encoder.hpp)
- the null logger (accepts everything, never produces output)
## log levels
*cib* offers 6 well-known and 2 user-defined log levels, according to the [MIPI SyS-T spec](https://www.mipi.org/specifications/sys-t).
```c++
namespace logging {
enum level {
MAX = 0,
FATAL = 1,
ERROR = 2,
WARN = 3,
INFO = 4,
USER1 = 5,
USER2 = 6,
TRACE = 7
};
}
```
## interface
*cib* logging macros follow the log levels available:
```cpp
CIB_TRACE(...);
CIB_INFO(...);
CIB_WARN(...);
CIB_ERROR(...);
CIB_FATAL(...);
```
These are the same as calling `CIB_LOG` with the `logging::level` as the first argument.
`CIB_FATAL(...)` also causes a program termination according to the logging implementation.
`CIB_ASSERT(expression)` is also available and calls `CIB_FATAL` if the asserted expression is false.
In order to use logging in a header, it suffices only to include
[log.hpp](log.hpp) and use the macros. Header-only clients of logging do not
need to know the implementation selected.
To use logging in a translation unit, the TU needs to see a customization, which brings us to...
## selecting a logger
Programs can choose which logger to use by specializing the `logging::config` variable template.
Left unspecialized, the null logger will be used.
```cpp
// use libfmt logging to std::cout
template <>
inline auto logging::config<> = logging::fmt::config{std::ostream_iterator<char>{std::cout}};
```
The provided `libfmt` implementation can output to multiple destinations by constructing
`logging::fmt::config` with multiple `ostream` iterators.
***NOTE:*** Be sure that each translation unit sees the same specialization of
`logging::config<>`! Otherwise you will have an [ODR](https://en.cppreference.com/w/cpp/language/definition) violation.
## implementing a logger
Each logging implementation (configuration) provides a customization point: a
`logger` object, which must implement `log`.
Therefore providing a custom implementation is a matter of defining this
structure appropriately.
```cpp
namespace my_logger {
struct config {
struct {
template <logging::level L, typename... Ts>
auto log(Ts &&...ts) -> void {
// log the ts... according to my mechanism
}
} logger;
};
}
```
To use the custom implementation, specialize `logging::config`:
```cpp
// use my logger
template <>
inline auto logging::config<> = my_logger::config{};
```
*cib* uses a libfmt-inspired convention for logging: the first argument to `log` is a format string with `{}` as format placeholders.

View File

@@ -0,0 +1,19 @@
#pragma once
#include <log/level.hpp>
#include <cstdint>
namespace sc {
template <typename...> struct args;
template <typename, typename T, T...> struct undefined;
template <logging::level, typename> struct message {};
template <typename> struct module_string {};
} // namespace sc
using string_id = std::uint32_t;
using module_id = std::uint32_t;
template <typename> extern auto catalog() -> string_id;
template <typename> extern auto module() -> module_id;

View File

@@ -0,0 +1,224 @@
#pragma once
#include <conc/concurrency.hpp>
#include <log/catalog/catalog.hpp>
#include <log/log.hpp>
#include <log/module.hpp>
#include <msg/message.hpp>
#include <stdx/bit.hpp>
#include <stdx/compiler.hpp>
#include <stdx/ct_string.hpp>
#include <stdx/tuple.hpp>
#include <stdx/utility.hpp>
#include <algorithm>
#include <concepts>
#include <cstdint>
#include <string_view>
#include <utility>
namespace logging::mipi {
namespace detail {
template <auto L, typename S, typename... Args> constexpr auto to_message() {
constexpr auto s = S::value;
using char_t = typename std::remove_cv_t<decltype(s)>::value_type;
return [&]<std::size_t... Is>(std::integer_sequence<std::size_t, Is...>) {
return sc::message<
static_cast<logging::level>(L),
sc::undefined<sc::args<Args...>, char_t, s[Is]...>>{};
}(std::make_integer_sequence<std::size_t, std::size(s)>{});
}
template <stdx::ct_string S> constexpr auto to_module() {
constexpr auto s = std::string_view{S};
return [&]<std::size_t... Is>(std::integer_sequence<std::size_t, Is...>) {
return sc::module_string<sc::undefined<void, char, s[Is]...>>{};
}(std::make_integer_sequence<std::size_t, std::size(s)>{});
}
} // namespace detail
namespace defn {
using msg::at;
using msg::dword_index_t;
using msg::field;
using msg::message;
using msg::operator""_msb;
using msg::operator""_lsb;
enum struct type : uint8_t { Build = 0, Short32 = 1, Catalog = 3 };
enum struct build_subtype : uint8_t { Compact32 = 0, Compact64 = 1, Long = 2 };
enum struct catalog_subtype : uint8_t { Id32_Pack32 = 1 };
using type_f = field<"type", type>::located<at{dword_index_t{0}, 3_msb, 0_lsb}>;
using opt_len_f =
field<"opt_len", bool>::located<at{dword_index_t{0}, 9_msb, 9_lsb}>;
using payload_len_f =
field<"payload_len",
std::uint16_t>::located<at{dword_index_t{1}, 15_msb, 0_lsb}>;
using build_subtype_f =
field<"subtype",
build_subtype>::located<at{dword_index_t{0}, 29_msb, 24_lsb}>;
using compact32_build_id_f = field<"build_id", std::uint32_t>::located<
at{dword_index_t{0}, 31_msb, 30_lsb}, at{dword_index_t{0}, 23_msb, 4_lsb}>;
using compact64_build_id_f = field<"build_id", std::uint64_t>::located<
at{dword_index_t{1}, 31_msb, 0_lsb}, at{dword_index_t{0}, 31_msb, 30_lsb},
at{dword_index_t{0}, 23_msb, 4_lsb}>;
using normal_build_msg_t =
message<"normal_build", type_f::with_required<type::Build>,
opt_len_f::with_required<true>,
build_subtype_f::with_required<build_subtype::Long>, payload_len_f>;
using compact32_build_msg_t =
message<"compact32_build", type_f::with_required<type::Build>,
build_subtype_f::with_required<build_subtype::Compact32>,
compact32_build_id_f>;
using compact64_build_msg_t =
message<"compact64_build", type_f::with_required<type::Build>,
build_subtype_f::with_required<build_subtype::Compact64>,
compact64_build_id_f>;
using short32_payload_f =
field<"payload",
std::uint32_t>::located<at{dword_index_t{0}, 31_msb, 4_lsb}>;
using short32_msg_t =
message<"short32", type_f::with_required<type::Short32>, short32_payload_f>;
using catalog_subtype_f =
field<"subtype",
catalog_subtype>::located<at{dword_index_t{0}, 29_msb, 24_lsb}>;
using severity_f = field<"severity", std::uint8_t>::located<at{dword_index_t{0},
6_msb, 4_lsb}>;
using module_id_f =
field<"module_id",
std::uint8_t>::located<at{dword_index_t{0}, 22_msb, 16_lsb}>;
using catalog_msg_t =
message<"catalog", type_f::with_required<type::Catalog>, severity_f,
module_id_f,
catalog_subtype_f::with_required<catalog_subtype::Id32_Pack32>>;
} // namespace defn
template <typename TDestinations> struct log_handler {
constexpr explicit log_handler(TDestinations &&ds) : dests{std::move(ds)} {}
template <typename Env, typename FilenameStringType,
typename LineNumberType, typename MsgType>
ALWAYS_INLINE auto log(FilenameStringType, LineNumberType,
MsgType const &msg) -> void {
log_msg<Env>(msg);
}
template <typename Env, typename Msg>
ALWAYS_INLINE auto log_msg(Msg msg) -> void {
msg.apply([&]<typename S, typename... Args>(S, Args... args) {
constexpr auto L = stdx::to_underlying(get_level(Env{}).value);
using Message = decltype(detail::to_message<L, S, Args...>());
using Module =
decltype(detail::to_module<get_module(Env{}).value>());
dispatch_message<L>(catalog<Message>(), module<Module>(),
static_cast<std::uint32_t>(args)...);
});
}
template <auto Version, stdx::ct_string S = ""> auto log_build() -> void {
using namespace msg;
if constexpr (S.empty() and stdx::bit_width(Version) <= 22) {
owning<defn::compact32_build_msg_t> message{"build_id"_field =
Version};
dispatch_pass_by_args(message.data()[0]);
} else if constexpr (S.empty() and stdx::bit_width(Version) <= 54) {
owning<defn::compact64_build_msg_t> message{"build_id"_field =
Version};
dispatch_pass_by_args(message.data()[0], message.data()[1]);
} else {
constexpr auto header_size =
defn::normal_build_msg_t::size<std::uint8_t>::value;
constexpr auto payload_len = S.size() + sizeof(std::uint64_t);
using storage_t =
std::array<std::uint8_t, header_size + payload_len>;
defn::normal_build_msg_t::owner_t<storage_t> message{
"payload_len"_field = payload_len};
auto dest = &message.data()[header_size];
auto const ver = stdx::to_le(static_cast<std::uint64_t>(Version));
dest = std::copy_n(stdx::bit_cast<std::uint8_t const *>(&ver),
sizeof(std::uint64_t), dest);
std::copy_n(std::cbegin(S.value), S.size(), dest);
dispatch_pass_by_buffer(message.data());
}
}
private:
template <typename... MsgDataTypes>
NEVER_INLINE auto
dispatch_pass_by_args(MsgDataTypes &&...msg_data) -> void {
stdx::for_each(
[&]<typename Dest>(Dest &dest) {
conc::call_in_critical_section<Dest>([&] {
dest.log_by_args(std::forward<MsgDataTypes>(msg_data)...);
});
},
dests);
}
NEVER_INLINE auto
dispatch_pass_by_buffer(stdx::span<std::uint8_t const> msg) -> void {
stdx::for_each(
[&]<typename Dest>(Dest &dest) {
conc::call_in_critical_section<Dest>(
[&] { dest.log_by_buf(msg); });
},
dests);
}
template <auto Level, std::same_as<std::uint32_t>... MsgDataTypes>
ALWAYS_INLINE auto dispatch_message(string_id id,
[[maybe_unused]] module_id m,
MsgDataTypes... msg_data) -> void {
using namespace msg;
if constexpr (sizeof...(msg_data) == 0u) {
owning<defn::short32_msg_t> message{"payload"_field = id};
dispatch_pass_by_args(message.data()[0]);
} else if constexpr (sizeof...(MsgDataTypes) <= 2u) {
owning<defn::catalog_msg_t> message{"severity"_field = Level,
"module_id"_field = m};
dispatch_pass_by_args(
message.data()[0], stdx::to_le(id),
stdx::to_le(std::forward<MsgDataTypes>(msg_data))...);
} else {
constexpr auto header_size =
defn::catalog_msg_t::size<std::uint8_t>::value;
constexpr auto payload_len =
(sizeof(id) + ... + sizeof(MsgDataTypes));
using storage_t =
std::array<std::uint8_t, header_size + payload_len>;
defn::catalog_msg_t::owner_t<storage_t> message{
"severity"_field = Level, "module_id"_field = m};
constexpr auto copy_arg = [](std::uint32_t arg, auto &dest) {
std::memcpy(dest, &arg, sizeof(std::uint32_t));
dest += sizeof(std::uint32_t);
};
auto dest = &message.data()[header_size];
copy_arg(stdx::to_le(id), dest);
(copy_arg(stdx::to_le(msg_data), dest), ...);
dispatch_pass_by_buffer(message.data());
}
}
TDestinations dests;
};
template <typename... TDestinations> struct config {
using destinations_tuple_t = stdx::tuple<TDestinations...>;
constexpr explicit config(TDestinations... dests)
: logger{stdx::tuple{std::move(dests)...}} {}
log_handler<destinations_tuple_t> logger;
};
template <typename... Ts> config(Ts...) -> config<Ts...>;
} // namespace logging::mipi

View File

@@ -0,0 +1,116 @@
#pragma once
#include <stdx/compiler.hpp>
#include <stdx/ct_string.hpp>
#include <stdx/utility.hpp>
#include <boost/mp11/algorithm.hpp>
namespace logging {
template <auto Query, auto Value> struct prop {
[[nodiscard]] CONSTEVAL static auto query(decltype(Query)) noexcept {
return Value;
}
};
namespace detail {
template <typename Q, typename Env>
concept valid_query_for = requires { Env::query(Q{}); };
template <typename Q, typename... Envs>
concept valid_query_over = (... or valid_query_for<Q, Envs>);
template <typename Q> struct has_query {
template <typename Env>
using fn = std::bool_constant<valid_query_for<Q, Env>>;
};
} // namespace detail
template <typename... Envs> struct env {
template <detail::valid_query_over<Envs...> Q>
CONSTEVAL static auto query(Q) noexcept {
using I = boost::mp11::mp_find_if_q<boost::mp11::mp_list<Envs...>,
detail::has_query<Q>>;
using E = boost::mp11::mp_at<boost::mp11::mp_list<Envs...>, I>;
return Q{}(E{});
}
};
namespace detail {
template <typename T> struct autowrap {
// NOLINTNEXTLINE(google-explicit-constructor)
CONSTEVAL autowrap(T t) : value(t) {}
T value;
};
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
template <std::size_t N> using str_lit_t = char const (&)[N];
template <std::size_t N> struct autowrap<str_lit_t<N>> {
// NOLINTNEXTLINE(google-explicit-constructor)
CONSTEVAL autowrap(str_lit_t<N> str) : value(str) {}
stdx::ct_string<N> value;
};
template <typename T> autowrap(T) -> autowrap<T>;
template <std::size_t N> autowrap(str_lit_t<N>) -> autowrap<str_lit_t<N>>;
template <auto V> struct wrap {
constexpr static auto value = V;
};
template <typename> struct for_each_pair;
template <std::size_t... Is> struct for_each_pair<std::index_sequence<Is...>> {
template <auto... Args>
using type = env<
prop<boost::mp11::mp_at_c<boost::mp11::mp_list<wrap<Args>...>,
2 * Is>::value.value,
stdx::ct<boost::mp11::mp_at_c<boost::mp11::mp_list<wrap<Args>...>,
2 * Is + 1>::value.value>()>...>;
};
} // namespace detail
template <typename Env = env<>>
constexpr auto make_env = []<detail::autowrap... Args> {
using new_env_t = typename detail::for_each_pair<
std::make_index_sequence<sizeof...(Args) / 2>>::template type<Args...>;
return boost::mp11::mp_append<new_env_t, Env>{};
};
template <detail::autowrap... Args>
using make_env_t = decltype(make_env<>.template operator()<Args...>());
} // namespace logging
using cib_log_env_t = logging::env<>;
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#ifdef __clang__
#define CIB_PRAGMA_SEMI
#else
#define CIB_PRAGMA_SEMI ;
#endif
#define CIB_LOG_ENV_DECL(...) \
[[maybe_unused]] typedef decltype([]<logging::detail:: \
autowrap... _env_args> { \
using new_env_t = \
typename logging::detail::for_each_pair<std::make_index_sequence< \
sizeof...(_env_args) / 2>>::template type<_env_args...>; \
return boost::mp11::mp_append<new_env_t, cib_log_env_t>{}; \
}.template operator()<__VA_ARGS__>()) cib_log_env_t
#define CIB_LOG_ENV(...) \
STDX_PRAGMA(diagnostic push) \
STDX_PRAGMA(diagnostic ignored "-Wshadow") \
CIB_LOG_ENV_DECL(__VA_ARGS__) \
CIB_PRAGMA_SEMI \
STDX_PRAGMA(diagnostic pop)
#define CIB_WITH_LOG_ENV(...) \
STDX_PRAGMA(diagnostic push) \
STDX_PRAGMA(diagnostic ignored "-Wshadow") \
if constexpr (CIB_LOG_ENV_DECL(__VA_ARGS__); true) \
STDX_PRAGMA(diagnostic pop)
// NOLINTEND(cppcoreguidelines-macro-usage)

View File

@@ -0,0 +1,68 @@
#pragma once
#include <log/level.hpp>
#include <log/log.hpp>
#include <log/module.hpp>
#include <stdx/ct_format.hpp>
#include <stdx/tuple.hpp>
#include <stdx/tuple_algorithms.hpp>
#include <stdx/utility.hpp>
#include <fmt/format.h>
#include <chrono>
#include <type_traits>
#include <utility>
template <logging::level L>
struct fmt::formatter<std::integral_constant<logging::level, L>> {
constexpr static auto parse(format_parse_context &ctx) {
return ctx.begin();
}
template <typename FormatContext>
auto format(std::integral_constant<logging::level, L>,
FormatContext &ctx) const {
return ::fmt::format_to(ctx.out(), logging::to_text<L>());
}
};
namespace logging::fmt {
template <typename TDestinations> struct log_handler {
constexpr explicit log_handler(TDestinations &&ds) : dests{std::move(ds)} {}
template <typename Env, typename FilenameStringType,
typename LineNumberType, typename MsgType>
auto log(FilenameStringType, LineNumberType, MsgType const &msg) -> void {
auto const currentTime =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start_time)
.count();
stdx::for_each(
[&](auto &out) {
::fmt::format_to(out, "{:>8}us {} [{}]: ", currentTime,
get_level(Env{}), get_module(Env{}).value);
msg.apply(
[&]<typename StringType>(StringType, auto const &...args) {
::fmt::format_to(out, StringType::value, args...);
});
*out = '\n';
},
dests);
}
private:
static inline auto const start_time = std::chrono::steady_clock::now();
TDestinations dests;
};
template <typename... TDestinations> struct config {
using destinations_tuple_t = stdx::tuple<TDestinations...>;
constexpr explicit config(TDestinations... dests)
: logger{stdx::tuple{std::move(dests)...}} {}
log_handler<destinations_tuple_t> logger;
};
} // namespace logging::fmt

View File

@@ -0,0 +1,44 @@
#pragma once
#include <log/env.hpp>
#include <stdx/type_traits.hpp>
#include <array>
#include <cstdint>
#include <string_view>
#include <type_traits>
#include <utility>
namespace logging {
// enum assignment is according to Mipi_Sys-T Severity definition
enum struct level : std::uint8_t {
MAX = 0,
FATAL = 1,
ERROR = 2,
WARN = 3,
INFO = 4,
USER1 = 5,
USER2 = 6,
TRACE = 7
};
template <level L>
[[nodiscard]] constexpr auto to_text() -> std::string_view
requires(L <= level::TRACE)
{
using namespace std::string_view_literals;
constexpr std::array level_text{"MAX"sv, "FATAL"sv, "ERROR"sv, "WARN"sv,
"INFO"sv, "USER1"sv, "USER2"sv, "TRACE"sv};
return level_text[stdx::to_underlying(L)];
}
[[maybe_unused]] constexpr inline struct get_level_t {
template <typename T>
CONSTEVAL auto operator()(T &&t) const noexcept(
noexcept(std::forward<T>(t).query(std::declval<get_level_t>())))
-> decltype(std::forward<T>(t).query(*this)) {
return std::forward<T>(t).query(*this);
}
} get_level;
} // namespace logging

View File

@@ -0,0 +1,115 @@
#pragma once
#include <log/env.hpp>
#include <log/level.hpp>
#include <log/module.hpp>
#include <sc/format.hpp>
#include <sc/fwd.hpp>
#include <stdx/compiler.hpp>
#include <stdx/ct_string.hpp>
#include <stdx/panic.hpp>
#include <cstdint>
#include <utility>
namespace version {
namespace null {
struct config {
constexpr static auto build_id = std::uint64_t{};
constexpr static auto version_string = stdx::ct_string{""};
};
} // namespace null
template <typename...> inline auto config = null::config{};
} // namespace version
namespace logging {
namespace null {
struct config {
struct {
template <typename>
constexpr auto log(auto &&...) const noexcept -> void {}
} logger;
};
} // namespace null
template <typename...> inline auto config = null::config{};
struct default_flavor_t;
template <typename Flavor, typename... Ts>
ALWAYS_INLINE 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, typename Env, typename... Ts, typename... TArgs>
ALWAYS_INLINE static auto log(TArgs &&...args) -> void {
auto &cfg = get_config<Flavor, Ts...>();
cfg.logger.template log<Env>(std::forward<TArgs>(args)...);
}
} // namespace logging
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#define CIB_LOG(FLAVOR, MSG, ...) \
logging::log<FLAVOR, cib_log_env_t>( \
__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_TRACE(...) \
CIB_LOG_WITH_LEVEL(logging::level::TRACE __VA_OPT__(, ) __VA_ARGS__)
#define CIB_INFO(...) \
CIB_LOG_WITH_LEVEL(logging::level::INFO __VA_OPT__(, ) __VA_ARGS__)
#define CIB_WARN(...) \
CIB_LOG_WITH_LEVEL(logging::level::WARN __VA_OPT__(, ) __VA_ARGS__)
#define CIB_ERROR(...) \
CIB_LOG_WITH_LEVEL(logging::level::ERROR __VA_OPT__(, ) __VA_ARGS__)
#define CIB_FATAL(MSG, ...) \
[](auto &&str) { \
CIB_LOG_ENV(logging::get_level, logging::level::FATAL); \
logging::log<logging::default_flavor_t, cib_log_env_t>(__FILE__, \
__LINE__, str); \
FWD(str).apply([]<typename S, typename... Args>(S s, Args... args) { \
constexpr auto cts = stdx::ct_string_from_type(s); \
stdx::panic<cts>(args...); \
}); \
}(sc::format(MSG##_sc __VA_OPT__(, ) __VA_ARGS__))
#define CIB_ASSERT(expr) \
((expr) ? void(0) : CIB_FATAL("Assertion failure: " #expr))
namespace logging {
template <typename Flavor, typename... Ts>
ALWAYS_INLINE 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_ENV(logging::get_level, logging::level::MAX);
l_cfg.logger.template log<cib_log_env_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_V(FLAVOR) logging::log_version<FLAVOR>()
#define CIB_LOG_VERSION() CIB_LOG_V(logging::default_flavor_t)
// NOLINTEND(cppcoreguidelines-macro-usage)

View File

@@ -0,0 +1,27 @@
#pragma once
#include <log/env.hpp>
#include <stdx/ct_string.hpp>
#include <utility>
namespace logging {
[[maybe_unused]] constexpr inline struct get_module_t {
template <typename T>
requires true // more constrained
CONSTEVAL auto operator()(T &&t) const noexcept(
noexcept(std::forward<T>(t).query(std::declval<get_module_t>())))
-> decltype(std::forward<T>(t).query(*this)) {
return std::forward<T>(t).query(*this);
}
CONSTEVAL auto operator()(auto &&) const {
using namespace stdx::literals;
return "default"_ctst;
}
} get_module;
} // namespace logging
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CIB_LOG_MODULE(S) CIB_LOG_ENV(logging::get_module, S)