lg2: support sdbusplus enum conversion
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I78cb28697631499adf5be64f7157a894c8d887ee
diff --git a/docs/structured-logging.md b/docs/structured-logging.md
index 957485d..74c3e41 100644
--- a/docs/structured-logging.md
+++ b/docs/structured-logging.md
@@ -108,7 +108,7 @@
The APIs can handle (and format appropriately) any data of the following
types: signed or unsigned integers, floating point numbers, booleans, strings
-(C-strings, std::strings, or std::string_views), and pointers.
+(C-strings, std::strings, or std::string_views), sdbusplus enums, and pointers.
The APIs also perform compile-time analysis of the arguments to give descriptive
error messages for incorrect parameters or format flags. Some examples are:
diff --git a/lib/include/phosphor-logging/lg2/conversion.hpp b/lib/include/phosphor-logging/lg2/conversion.hpp
index beb3cf0..4c860d4 100644
--- a/lib/include/phosphor-logging/lg2/conversion.hpp
+++ b/lib/include/phosphor-logging/lg2/conversion.hpp
@@ -5,6 +5,7 @@
#include <phosphor-logging/lg2/flags.hpp>
#include <phosphor-logging/lg2/level.hpp>
#include <phosphor-logging/lg2/logger.hpp>
+#include <sdbusplus/message/native_types.hpp>
#include <source_location>
#include <string_view>
#include <tuple>
@@ -43,6 +44,9 @@
concept unsigned_integral_except_bool =
!std::same_as<T, bool> && std::unsigned_integral<T>;
+template <typename T>
+concept sdbusplus_enum = sdbusplus::message::has_convert_from_string_v<T>;
+
/** Concept listing all of the types we know how to convert into a format
* for logging.
*/
@@ -50,7 +54,7 @@
concept unsupported_log_convert_types =
!(unsigned_integral_except_bool<T> || std::signed_integral<T> ||
std::same_as<bool, T> || std::floating_point<T> || string_like_type<T> ||
- pointer_type<T>);
+ pointer_type<T> || sdbusplus_enum<T>);
/** Any type we do not know how to convert for logging gives a nicer
* static_assert message. */
@@ -135,6 +139,26 @@
return std::make_tuple(h, (f | floating).value, static_cast<double>(v));
}
+/** Logging conversion for sdbusplus enums. */
+template <log_flags... Fs, sdbusplus_enum V>
+static auto log_convert(const char* h, log_flag<Fs...> f, V v)
+{
+ // Compile-time checks for valid formatting flags.
+ prohibit(f, bin);
+ prohibit(f, dec);
+ prohibit(f, field16);
+ prohibit(f, field32);
+ prohibit(f, field64);
+ prohibit(f, field8);
+ prohibit(f, floating);
+ prohibit(f, hex);
+ prohibit(f, signed_val);
+ prohibit(f, unsigned_val);
+
+ return std::make_tuple(h, (f | str).value,
+ sdbusplus::message::convert_to_string(v));
+}
+
/** Logging conversion for string-likes. */
template <log_flags... Fs, string_like_type V>
static auto log_convert(const char* h, log_flag<Fs...> f, V&& v)
@@ -206,10 +230,36 @@
do_log(l, s, m, sizeof...(Ts) / 3, std::forward<Ts>(ts)...);
}
+ /** Apply the tuple from the end of 'step' into done.
+ *
+ * There are some cases where the tuple must hold a `std::string` in
+ * order to avoid losing data in a temporary (see sdbusplus-enum
+ * conversion), but the `do_log` function wants a `const char*` as
+ * the variadic type. Run the tuple through a lambda to pull out
+ * the `const char*`'s without losing the temporary held by the tuple.
+ */
+ static void apply_done(const auto& args_tuple)
+ {
+ auto squash_string = [](auto& arg) -> decltype(auto) {
+ if constexpr (std::is_same_v<const std::string&, decltype(arg)>)
+ {
+ return arg.data();
+ }
+ else
+ {
+ return arg;
+ }
+ };
+
+ std::apply([squash_string](
+ const auto&... args) { done(squash_string(args)...); },
+ args_tuple);
+ }
+
/** Handle conversion of a { Header, Flags, Value } argument set. */
template <typename... Ts, std::convertible_to<const char*> H,
log_flags... Fs, typename V, typename... Ss>
- static void step(std::tuple<Ts...> ts, H&& h, log_flag<Fs...> f, V&& v,
+ static void step(std::tuple<Ts...>&& ts, H&& h, log_flag<Fs...> f, V&& v,
Ss&&... ss)
{
// These two if conditions are similar, except that one calls 'done'
@@ -222,13 +272,13 @@
if constexpr (sizeof...(Ss) == 0)
{
- std::apply(
- [](auto... args) { done(args...); },
- std::tuple_cat(ts, log_convert(std::forward<H>(h), f, v)));
+ apply_done(std::tuple_cat(std::move(ts),
+ log_convert(std::forward<H>(h), f, v)));
}
else
{
- step(std::tuple_cat(ts, log_convert(std::forward<H>(h), f, v)),
+ step(std::tuple_cat(std::move(ts),
+ log_convert(std::forward<H>(h), f, v)),
std::forward<Ss>(ss)...);
}
}
@@ -236,7 +286,7 @@
/** Handle conversion of a { Header, Value } argument set. */
template <typename... Ts, std::convertible_to<const char*> H, typename V,
typename... Ss>
- static void step(std::tuple<Ts...> ts, H&& h, V&& v, Ss&&... ss)
+ static void step(std::tuple<Ts...>&& ts, H&& h, V&& v, Ss&&... ss)
{
// These two if conditions are similar, except that one calls 'done'
// since Ss is empty and the other calls the next 'step'.
@@ -248,14 +298,14 @@
if constexpr (sizeof...(Ss) == 0)
{
- std::apply([](auto... args) { done(args...); },
- std::tuple_cat(ts, log_convert(std::forward<H>(h),
- log_flag<>{}, v)));
+ apply_done(
+ std::tuple_cat(std::move(ts), log_convert(std::forward<H>(h),
+ log_flag<>{}, v)));
}
else
{
- step(std::tuple_cat(
- ts, log_convert(std::forward<H>(h), log_flag<>{}, v)),
+ step(std::tuple_cat(std::move(ts), log_convert(std::forward<H>(h),
+ log_flag<>{}, v)),
std::forward<Ss>(ss)...);
}
}
@@ -263,7 +313,7 @@
/** Finding a non-string as the first argument of a 2 or 3 argument set
* is an error (missing HEADER field). */
template <typename... Ts, any_but<const char*> H, typename... Ss>
- static void step(const std::tuple<Ts...>&, H, Ss&&...)
+ static void step(std::tuple<Ts...>&&, H, Ss&&...)
{
static_assert(std::is_same_v<const char*, H>,
"Found value without expected header field.");
@@ -272,7 +322,7 @@
/** Finding a free string at the end is an error (found HEADER but no data).
*/
template <typename... Ts, std::convertible_to<const char*> H>
- static void step(const std::tuple<Ts...>&, H)
+ static void step(std::tuple<Ts...>&&, H)
{
static_assert(!std::is_convertible_v<const char*, H>,
"Found header field without expected data.");