1 #pragma once
2
3 #include <phosphor-logging/lg2/flags.hpp>
4 #include <phosphor-logging/lg2/header.hpp>
5 #include <phosphor-logging/lg2/level.hpp>
6 #include <phosphor-logging/lg2/logger.hpp>
7 #include <sdbusplus/message/native_types.hpp>
8
9 #include <concepts>
10 #include <cstddef>
11 #include <filesystem>
12 #include <format>
13 #include <source_location>
14 #include <string_view>
15 #include <tuple>
16 #include <type_traits>
17
18 namespace lg2::details
19 {
20
21 /** Concept to determine if an item acts like a string.
22 *
23 * Something acts like a string if we can construct a string_view from it.
24 * This covers std::string and C-strings. But, there is subtlety in that
25 * nullptr_t can be used to construct a string_view until C++23, so we need
26 * to exempt out nullptr_t's (otherwise `nullptr` ends up acting like a
27 * string).
28 */
29 template <typename T>
30 concept string_like_type =
31 (std::constructible_from<std::string_view, T> ||
32 std::same_as<std::filesystem::path, std::decay_t<T>>) &&
33 !std::same_as<std::nullptr_t, T>;
34
35 /** Concept to determine if an item acts like a pointer.
36 *
37 * Any pointer, which doesn't act like a string, should be treated as a raw
38 * pointer.
39 */
40 template <typename T>
41 concept pointer_type =
42 (std::is_pointer_v<T> || std::same_as<std::nullptr_t, T>) &&
43 !string_like_type<T>;
44
45 /** Concept to determine if an item acts like an unsigned_integral.
46 *
47 * Except bool because we want bool to be handled special to create nice
48 * `True` and `False` strings.
49 */
50 template <typename T>
51 concept unsigned_integral_except_bool =
52 !std::same_as<T, bool> && std::unsigned_integral<T>;
53
54 template <typename T>
55 concept sdbusplus_enum = sdbusplus::message::has_convert_from_string_v<T>;
56
57 template <typename T>
58 concept exception_type = std::derived_from<std::decay_t<T>, std::exception>;
59
60 template <typename T>
61 concept sdbusplus_object_path =
62 std::derived_from<std::decay_t<T>, sdbusplus::message::object_path>;
63
64 template <typename T>
65 concept has_to_string = requires(T&& t) { to_string(t); };
66
67 template <typename T>
68 concept is_raw_enum =
69 std::is_enum_v<std::decay_t<T>> && !sdbusplus_enum<T> && !has_to_string<T>;
70
71 /** Concept listing all of the types we know how to convert into a format
72 * for logging.
73 */
74 template <typename T>
75 concept unsupported_log_convert_types =
76 !(unsigned_integral_except_bool<T> || std::signed_integral<T> ||
77 std::same_as<bool, T> || std::floating_point<T> || string_like_type<T> ||
78 pointer_type<T> || sdbusplus_enum<T> || exception_type<T> ||
79 sdbusplus_object_path<T> || has_to_string<T> || is_raw_enum<T>);
80
81 /** Any type we do not know how to convert for logging gives a nicer
82 * static_assert message. */
83 template <log_flags... Fs, unsupported_log_convert_types T>
log_convert(const char *,log_flag<Fs...>,T)84 static auto log_convert(const char*, log_flag<Fs...>, T)
85 {
86 static_assert(!std::is_same_v<T, T>, "Unsupported type for logging value.");
87 // Having this return of an empty tuple reduces the error messages.
88 return std::tuple<>{};
89 }
90
91 /** Logging conversion for unsigned. */
92 template <log_flags... Fs, unsigned_integral_except_bool V>
log_convert(const char * h,log_flag<Fs...> f,V v)93 static auto log_convert(const char* h, log_flag<Fs...> f, V v)
94 {
95 // Compile-time checks for valid formatting flags.
96 prohibit(f, floating);
97 prohibit(f, signed_val);
98 prohibit(f, str);
99
100 one_from_set(f, dec | hex | bin);
101 one_from_set(f, field8 | field16 | field32 | field64);
102
103 // Add 'unsigned' flag, force to uint64_t for variadic passing.
104 return std::make_tuple(h, (f | unsigned_val).value,
105 static_cast<uint64_t>(v));
106 }
107
108 /** Logging conversion for signed. */
109 template <log_flags... Fs, std::signed_integral V>
log_convert(const char * h,log_flag<Fs...> f,V v)110 static auto log_convert(const char* h, log_flag<Fs...> f, V v)
111 {
112 // Compile-time checks for valid formatting flags.
113 prohibit(f, floating);
114 prohibit(f, str);
115 prohibit(f, unsigned_val);
116
117 one_from_set(f, dec | hex | bin);
118 one_from_set(f, field8 | field16 | field32 | field64);
119
120 // Add 'signed' flag, force to int64_t for variadic passing.
121 return std::make_tuple(h, (f | signed_val).value, static_cast<int64_t>(v));
122 }
123
124 /** Logging conversion for bool. */
125 template <log_flags... Fs, std::same_as<bool> V>
log_convert(const char * h,log_flag<Fs...> f,V v)126 static auto log_convert(const char* h, log_flag<Fs...> f, V v)
127 {
128 // Compile-time checks for valid formatting flags.
129 prohibit(f, bin);
130 prohibit(f, dec);
131 prohibit(f, field16);
132 prohibit(f, field32);
133 prohibit(f, field64);
134 prohibit(f, field8);
135 prohibit(f, floating);
136 prohibit(f, hex);
137 prohibit(f, signed_val);
138 prohibit(f, unsigned_val);
139
140 // Cast bools to a "True" or "False" string.
141 return std::make_tuple(h, (f | str).value, v ? "True" : "False");
142 }
143
144 /** Logging conversion for floating points. */
145 template <log_flags... Fs, std::floating_point V>
log_convert(const char * h,log_flag<Fs...> f,V v)146 static auto log_convert(const char* h, log_flag<Fs...> f, V v)
147 {
148 // Compile-time checks for valid formatting flags.
149 prohibit(f, bin);
150 prohibit(f, dec);
151 prohibit(f, field16);
152 prohibit(f, field32);
153 prohibit(f, field64);
154 prohibit(f, field8);
155 prohibit(f, hex);
156 prohibit(f, signed_val);
157 prohibit(f, str);
158 prohibit(f, unsigned_val);
159
160 // Add 'floating' flag, force to double for variadic passing.
161 return std::make_tuple(h, (f | floating).value, static_cast<double>(v));
162 }
163
164 /** Logging conversion for sdbusplus enums. */
165 template <log_flags... Fs, sdbusplus_enum V>
log_convert(const char * h,log_flag<Fs...> f,V v)166 static auto log_convert(const char* h, log_flag<Fs...> f, V v)
167 {
168 // Compile-time checks for valid formatting flags.
169 prohibit(f, bin);
170 prohibit(f, dec);
171 prohibit(f, field16);
172 prohibit(f, field32);
173 prohibit(f, field64);
174 prohibit(f, field8);
175 prohibit(f, floating);
176 prohibit(f, hex);
177 prohibit(f, signed_val);
178 prohibit(f, unsigned_val);
179
180 return std::make_tuple(h, (f | str).value,
181 sdbusplus::message::convert_to_string(v));
182 }
183
184 /** Logging conversion for string-likes. */
185 template <log_flags... Fs, string_like_type V>
log_convert(const char * h,log_flag<Fs...> f,const V & v)186 static auto log_convert(const char* h, log_flag<Fs...> f, const V& v)
187 {
188 // Compile-time checks for valid formatting flags.
189 prohibit(f, bin);
190 prohibit(f, dec);
191 prohibit(f, field16);
192 prohibit(f, field32);
193 prohibit(f, field64);
194 prohibit(f, field8);
195 prohibit(f, floating);
196 prohibit(f, hex);
197 prohibit(f, signed_val);
198 prohibit(f, unsigned_val);
199
200 // Utiilty to handle conversion to a 'const char*' depending on V:
201 // - 'const char*' and similar use static cast.
202 // - 'std::filesystem::path' use c_str() function.
203 // - 'std::string' and 'std::string_view' use data() function.
204 auto str_data = [](const V& v) {
205 if constexpr (std::is_same_v<const char*, std::decay_t<V>> ||
206 std::is_same_v<char*, std::decay_t<V>>)
207 {
208 return static_cast<const char*>(v);
209 }
210 else if constexpr (std::is_same_v<std::filesystem::path,
211 std::decay_t<V>>)
212 {
213 return v.c_str();
214 }
215 else
216 {
217 return v.data();
218 }
219 };
220
221 // Add 'str' flag, force to 'const char*' for variadic passing.
222 return std::make_tuple(h, (f | str).value, str_data(v));
223 }
224
225 /** Logging conversion for pointer-types. */
226 template <log_flags... Fs, pointer_type V>
log_convert(const char * h,log_flag<Fs...> f,V v)227 static auto log_convert(const char* h, log_flag<Fs...> f, V v)
228 {
229 // Compile-time checks for valid formatting flags.
230 prohibit(f, bin);
231 prohibit(f, dec);
232 prohibit(f, field16);
233 prohibit(f, field32);
234 prohibit(f, field64);
235 prohibit(f, field8);
236 prohibit(f, floating);
237 prohibit(f, signed_val);
238 prohibit(f, str);
239
240 // Cast (void*) to a hex-formatted uint64 using the target's pointer-size
241 // to determine field-width.
242 constexpr static auto new_f =
243 sizeof(void*) == 4 ? field32.value : field64.value;
244
245 return std::make_tuple(h, new_f | (hex | unsigned_val).value,
246 reinterpret_cast<uint64_t>(v));
247 }
248
249 /* Logging conversion for exceptions. */
250 template <log_flags... Fs, exception_type V>
log_convert(const char * h,log_flag<Fs...> f,V && v)251 static auto log_convert(const char* h, log_flag<Fs...> f, V&& v)
252 {
253 // Compile-time checks for valid formatting flags.
254 prohibit(f, bin);
255 prohibit(f, dec);
256 prohibit(f, field16);
257 prohibit(f, field32);
258 prohibit(f, field64);
259 prohibit(f, field8);
260 prohibit(f, floating);
261 prohibit(f, hex);
262 prohibit(f, signed_val);
263 prohibit(f, unsigned_val);
264
265 // Treat like a string, but get the 'what' from the exception.
266 return std::make_tuple(h, (f | str).value, v.what());
267 }
268
269 /* Logging conversion for object path. */
270 template <log_flags... Fs, sdbusplus_object_path V>
log_convert(const char * h,log_flag<Fs...> f,V && v)271 static auto log_convert(const char* h, log_flag<Fs...> f, V&& v)
272 {
273 // Compile-time checks for valid formatting flags.
274 prohibit(f, bin);
275 prohibit(f, dec);
276 prohibit(f, field16);
277 prohibit(f, field32);
278 prohibit(f, field64);
279 prohibit(f, field8);
280 prohibit(f, floating);
281 prohibit(f, hex);
282 prohibit(f, signed_val);
283 prohibit(f, unsigned_val);
284
285 // Treat like a string, but get the 'str' from the object path.
286 return std::make_tuple(h, (f | str).value, v.str);
287 }
288
289 template <log_flags... Fs, has_to_string V>
log_convert(const char * h,log_flag<Fs...> f,V && v)290 static auto log_convert(const char* h, log_flag<Fs...> f, V&& v)
291 {
292 // Compile-time checks for valid formatting flags.
293 prohibit(f, bin);
294 prohibit(f, dec);
295 prohibit(f, field16);
296 prohibit(f, field32);
297 prohibit(f, field64);
298 prohibit(f, field8);
299 prohibit(f, floating);
300 prohibit(f, hex);
301 prohibit(f, signed_val);
302 prohibit(f, unsigned_val);
303
304 // Treat like a string, but call to_string.
305 return std::make_tuple(h, (f | str).value, to_string(std::forward<V>(v)));
306 }
307
308 template <log_flags... Fs, is_raw_enum V>
log_convert(const char * h,log_flag<Fs...> f,V && v)309 static auto log_convert(const char* h, log_flag<Fs...> f, V&& v)
310 {
311 // Compile-time checks for valid formatting flags.
312 prohibit(f, bin);
313 prohibit(f, dec);
314 prohibit(f, field16);
315 prohibit(f, field32);
316 prohibit(f, field64);
317 prohibit(f, field8);
318 prohibit(f, floating);
319 prohibit(f, hex);
320 prohibit(f, signed_val);
321 prohibit(f, unsigned_val);
322
323 // Treat like a string, but convert using std::format.
324 return std::make_tuple(
325 h, (f | str).value,
326 std::format("Enum({})",
327 static_cast<std::underlying_type_t<std::decay_t<V>>>(v)));
328 }
329
330 /** Class to facilitate walking through the arguments of the `lg2::log` function
331 * and ensuring correct parameter types and conversion operations.
332 */
333 class log_conversion
334 {
335 private:
336 /** Conversion and validation is complete. Pass along to the final
337 * do_log variadic function. */
338 template <typename... Ts>
done(level l,const std::source_location & s,const char * m,Ts &&...ts)339 static void done(level l, const std::source_location& s, const char* m,
340 Ts&&... ts)
341 {
342 do_log(l, s, m, ts..., nullptr);
343 }
344
345 /** Apply the tuple from the end of 'step' into done.
346 *
347 * There are some cases where the tuple must hold a `std::string` in
348 * order to avoid losing data in a temporary (see sdbusplus-enum
349 * conversion), but the `do_log` function wants a `const char*` as
350 * the variadic type. Run the tuple through a lambda to pull out
351 * the `const char*`'s without losing the temporary held by the tuple.
352 */
apply_done(const auto & args_tuple)353 static void apply_done(const auto& args_tuple)
354 {
355 auto squash_string = [](auto& arg) -> decltype(auto) {
356 if constexpr (std::is_same_v<const std::string&, decltype(arg)>)
357 {
358 return arg.data();
359 }
360 else
361 {
362 return arg;
363 }
364 };
365
366 std::apply([squash_string](
367 const auto&... args) { done(squash_string(args)...); },
368 args_tuple);
369 }
370
371 /** Handle conversion of a { Header, Flags, Value } argument set. */
372 template <typename... Ts, log_flags... Fs, typename V, typename... Ss>
step(std::tuple<Ts...> && ts,const header_str & h,log_flag<Fs...> f,V && v,Ss &&...ss)373 static void step(std::tuple<Ts...>&& ts, const header_str& h,
374 log_flag<Fs...> f, V&& v, Ss&&... ss)
375 {
376 static_assert(!std::is_same_v<header_str, std::decay_t<V>>,
377 "Found header_str as value; suggest using std::string to "
378 "avoid unintended conversion.");
379
380 // These two if conditions are similar, except that one calls 'done'
381 // since Ss is empty and the other calls the next 'step'.
382
383 // 1. Call `log_convert` on {h, f, v} for proper conversion.
384 // 2. Append the results of `log_convert` into the already handled
385 // arguments (in ts).
386 // 3. Call the next step in the chain to handle the remainder Ss.
387
388 if constexpr (sizeof...(Ss) == 0)
389 {
390 apply_done(
391 std::tuple_cat(std::move(ts), log_convert(h.data(), f, v)));
392 }
393 else
394 {
395 step(std::tuple_cat(std::move(ts), log_convert(h.data(), f, v)),
396 ss...);
397 }
398 }
399
400 /** Handle conversion of a { Header, Value } argument set. */
401 template <typename... Ts, typename V, typename... Ss>
step(std::tuple<Ts...> && ts,const header_str & h,V && v,Ss &&...ss)402 static void step(std::tuple<Ts...>&& ts, const header_str& h, V&& v,
403 Ss&&... ss)
404 {
405 static_assert(!std::is_same_v<header_str, std::decay_t<V>>,
406 "Found header_str as value; suggest using std::string to "
407 "avoid unintended conversion.");
408 // These two if conditions are similar, except that one calls 'done'
409 // since Ss is empty and the other calls the next 'step'.
410
411 // 1. Call `log_convert` on {h, <empty flags>, v} for proper conversion.
412 // 2. Append the results of `log_convert` into the already handled
413 // arguments (in ts).
414 // 3. Call the next step in the chain to handle the remainder Ss.
415
416 if constexpr (sizeof...(Ss) == 0)
417 {
418 apply_done(std::tuple_cat(std::move(ts),
419 log_convert(h.data(), log_flag<>{}, v)));
420 }
421 else
422 {
423 step(std::tuple_cat(std::move(ts),
424 log_convert(h.data(), log_flag<>{}, v)),
425 ss...);
426 }
427 }
428
429 /** Finding a non-string as the first argument of a 2 or 3 argument set
430 * is an error (missing HEADER field). */
431 template <typename... Ts, not_constexpr_string H, typename... Ss>
step(std::tuple<Ts...> &&,H,Ss &&...)432 static void step(std::tuple<Ts...>&&, H, Ss&&...)
433 {
434 static_assert(std::is_same_v<header_str, H>,
435 "Found value without expected header field.");
436 }
437
438 /** Finding a free string at the end is an error (found HEADER but no data).
439 */
440 template <typename... Ts>
step(std::tuple<Ts...> &&,header_str)441 static void step(std::tuple<Ts...>&&, header_str)
442 {
443 static_assert(std::is_same_v<std::tuple<Ts...>, header_str>,
444 "Found header field without expected data.");
445 }
446
447 public:
448 /** Start processing a sequence of arguments to `lg2::log` using `step` or
449 * `done`. */
450 template <typename... Ts>
start(level l,const std::source_location & s,const char * msg,Ts &&...ts)451 static void start(level l, const std::source_location& s, const char* msg,
452 Ts&&... ts)
453 {
454 // If there are no arguments (ie. just a message), then skip processing
455 // and call `done` directly.
456 if constexpr (sizeof...(Ts) == 0)
457 {
458 done(l, s, msg);
459 }
460 // Handle all the Ts by recursively calling 'step'.
461 else
462 {
463 step(std::forward_as_tuple(l, s, msg), ts...);
464 }
465 }
466 };
467
468 } // namespace lg2::details
469