quill
Codec.h
1 
7 #pragma once
8 
9 #include "quill/bundled/fmt/base.h"
10 
11 #include "quill/core/Attributes.h"
12 #include "quill/core/DynamicFormatArgStore.h"
13 #include "quill/core/InlinedVector.h"
14 
15 #include <cassert>
16 #include <cstddef>
17 #include <cstdint>
18 #include <cstring>
19 #include <limits>
20 #include <string>
21 #include <string_view>
22 #include <type_traits>
23 
24 QUILL_BEGIN_NAMESPACE
25 
26 namespace detail
27 {
28 
29 #if defined(_WIN32)
30  #if defined(__MINGW32__)
31  #pragma GCC diagnostic push
32  #pragma GCC diagnostic ignored "-Wredundant-decls"
33  #endif
34 
36 QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED extern std::string utf8_encode(std::wstring_view str);
37 QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED extern std::string utf8_encode(std::byte const* data,
38  size_t wide_str_len);
39 
40  #if defined(__MINGW32__)
41  #pragma GCC diagnostic pop
42  #endif
43 #endif
44 
48 template <class T>
50 {
51  typedef std::remove_cv_t<std::remove_reference_t<T>> type;
52 };
53 
54 template <class T>
55 using remove_cvref_t = typename remove_cvref<T>::type;
56 
57 template <class>
58 constexpr bool always_false_v = false;
59 
60 template <typename Arg>
61 void codec_not_found_for_type()
62 {
63  static_assert(
64  always_false_v<Arg>,
65  "\n"
66  "+------------------------------------------------------------------------------+\n"
67  "| Missing Codec for Type 'Arg' |\n"
68  "+------------------------------------------------------------------------------+\n"
69  "\n"
70  "Error: A codec for the specified type 'Arg' is not available.\n"
71  "\n"
72  "Possible solutions:\n"
73  "1. If Arg is an STL type:\n"
74  " - Ensure you have included the necessary headers for the specific STL type you are using "
75  "from the quill/std folder.\n"
76  "\n"
77  "2. If Arg is a user-defined type:\n"
78  " - Use either 'DeferredFormatCodec' or 'DirectFormatCodec'.\n"
79  " - Define a custom Codec for your type.\n"
80  " - Consider converting the value to a string before logging.\n"
81  "\n"
82  "Note: The specific type of 'Arg' can be found in the compiler error message.\n"
83  " Look for the instantiation of 'codec_not_found_for_type<Arg>' in the error output.\n"
84  " The compiler should indicate what type 'Arg' represents in this instance.\n"
85  "\n"
86  "For more information see https://quillcpp.readthedocs.io/en/latest/cheat_sheet.html\n");
87 }
88 
89 /***/
90 QUILL_NODISCARD inline size_t safe_strnlen(char const* str, size_t maxlen) noexcept
91 {
92  if (!str)
93  {
94  return 0;
95  }
96 
97 #if defined(__GNUC__) && !defined(__clang__)
98  // Suppress false positive in GCC - memchr safely stops at null terminator
99  #pragma GCC diagnostic push
100  #if __GNUC__ >= 13
101  #pragma GCC diagnostic ignored "-Wstringop-overread"
102  #endif
103 #endif
104 
105  auto end = static_cast<char const*>(std::memchr(str, '\0', maxlen));
106 
107 #if defined(__GNUC__) && !defined(__clang__)
108  #pragma GCC diagnostic pop
109 #endif
110 
111  return end ? static_cast<size_t>(end - str) : maxlen;
112 }
113 
114 /***/
115 QUILL_NODISCARD inline size_t safe_strnlen(char const* str) noexcept
116 {
117 #if defined(__i386__) || defined(__arm__)
118  // On i386, armel and armhf std::memchr "max number of bytes to examine" set to maximum size of
119  // unsigned int which does not compile
120  // Currently Debian package is using architecture `any` which includes them
121  static constexpr int32_t max_len = std::numeric_limits<int32_t>::max();
122 #else
123  static constexpr uint32_t max_len = std::numeric_limits<uint32_t>::max();
124 #endif
125 
126  return safe_strnlen(str, max_len);
127 }
129 template <typename T>
130 struct is_std_string : std::false_type
131 {
132 };
133 
134 template <typename Allocator>
135 struct is_std_string<std::basic_string<char, std::char_traits<char>, Allocator>> : std::true_type
136 {
137 };
138 } // namespace detail
139 
141 template <typename Arg, typename = void>
142 struct Codec
143 {
144  /***/
145  QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static size_t compute_encoded_size(
146  QUILL_MAYBE_UNUSED detail::SizeCacheVector& conditional_arg_size_cache, QUILL_MAYBE_UNUSED Arg const& arg) noexcept
147  {
148  if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>,
149  std::is_same<Arg, void const*>, std::is_same<Arg, void*>>)
150  {
151  return sizeof(Arg);
152  }
153  else if constexpr (std::conjunction_v<std::is_array<Arg>, std::is_same<detail::remove_cvref_t<std::remove_extent_t<Arg>>, char>>)
154  {
155  size_t constexpr N = std::extent_v<Arg>;
156  size_t len = detail::safe_strnlen(arg, N) + 1u;
157  if (QUILL_UNLIKELY(len > std::numeric_limits<uint32_t>::max()))
158  {
159  len = std::numeric_limits<uint32_t>::max();
160  }
161  return conditional_arg_size_cache.push_back(static_cast<uint32_t>(len));
162  }
163  else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>>)
164  {
165  // for c strings we do an additional check for nullptr
166  // include one extra for the zero termination
167  size_t len = detail::safe_strnlen(arg) + 1u;
168  if (QUILL_UNLIKELY(len > std::numeric_limits<uint32_t>::max()))
169  {
170  len = std::numeric_limits<uint32_t>::max();
171  }
172  return conditional_arg_size_cache.push_back(static_cast<uint32_t>(len));
173  }
174  else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
175  {
176  // for std::string we also need to store the size in order to correctly retrieve it
177  // the reason for this is that if we create e.g:
178  // std::string msg = fmtquill::format("{} {} {} {} {}", (char)0, (char)0, (char)0, (char)0,
179  // "sssssssssssssssssssssss"); then strlen(msg.data()) = 0 but msg.size() = 31
180  return sizeof(uint32_t) + static_cast<uint32_t>(arg.length());
181  }
182  else
183  {
184  detail::codec_not_found_for_type<Arg>();
185  return 0;
186  }
187  }
188 
189  /***/
190  QUILL_ATTRIBUTE_HOT static void encode(std::byte*& buffer,
191  QUILL_MAYBE_UNUSED detail::SizeCacheVector const& conditional_arg_size_cache,
192  QUILL_MAYBE_UNUSED uint32_t& conditional_arg_size_cache_index,
193  Arg const& arg) noexcept
194  {
195  if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>,
196  std::is_same<Arg, void const*>, std::is_same<Arg, void*>>)
197  {
198  std::memcpy(buffer, &arg, sizeof(Arg));
199  buffer += sizeof(Arg);
200  }
201  else if constexpr (std::conjunction_v<std::is_array<Arg>, std::is_same<detail::remove_cvref_t<std::remove_extent_t<Arg>>, char>>)
202  {
203  size_t constexpr N = std::extent_v<Arg>;
204  uint32_t const len = conditional_arg_size_cache[conditional_arg_size_cache_index++];
205 
206  if (QUILL_UNLIKELY(len > N))
207  {
208  // no '\0' in c array
209  assert(len == N + 1);
210  std::memcpy(buffer, arg, N);
211  buffer[N] = std::byte{'\0'};
212  }
213  else
214  {
215  std::memcpy(buffer, arg, len);
216  }
217 
218  buffer += len;
219  }
220  else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>>)
221  {
222  // null terminator is included in the len for c style strings
223  uint32_t const len = conditional_arg_size_cache[conditional_arg_size_cache_index++];
224 
225  if (arg)
226  {
227  // avoid gcc warning, even when size == 0
228  std::memcpy(buffer, arg, len - 1);
229  }
230 
231  buffer[len - 1] = std::byte{'\0'};
232  buffer += len;
233  }
234  else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
235  {
236  // for std::string we store the size first, in order to correctly retrieve it
237  // Copy the length first and then the actual string
238  auto const len = static_cast<uint32_t>(arg.length());
239  std::memcpy(buffer, &len, sizeof(len));
240  buffer += sizeof(len);
241  std::memcpy(buffer, arg.data(), len);
242  buffer += len;
243  }
244  else
245  {
246  detail::codec_not_found_for_type<Arg>();
247  }
248  }
249 
250  // We use two separate functions, decode_arg and decode_and_store_arg, because there are
251  // scenarios where we need to decode an argument without storing it in args_store, such as when
252  // dealing with nested types. Storing the argument requires a fmtquill formatter, so having
253  // two distinct functions allows us to avoid this requirement in cases where only decoding is needed.
254 
255  /***/
256  static auto decode_arg(std::byte*& buffer)
257  {
258  if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>,
259  std::is_same<Arg, void const*>, std::is_same<Arg, void*>>)
260  {
261  Arg arg;
262  std::memcpy(&arg, buffer, sizeof(Arg));
263  buffer += sizeof(Arg);
264  return arg;
265  }
266  else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>,
267  std::conjunction<std::is_array<Arg>, std::is_same<detail::remove_cvref_t<std::remove_extent_t<Arg>>, char>>>)
268  {
269  // c strings or char array
270  auto arg = reinterpret_cast<char const*>(buffer);
271  // for c_strings we add +1 to the length as we also want to copy the null terminated char
272  buffer += detail::safe_strnlen(arg) + 1u;
273  return arg;
274  }
275  else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
276  {
277  // for std::string we first need to retrieve the length
278  uint32_t len;
279  std::memcpy(&len, buffer, sizeof(len));
280  buffer += sizeof(len);
281  auto const arg = std::string_view{reinterpret_cast<char const*>(buffer), len};
282  buffer += len;
283  return arg;
284  }
285  else
286  {
287  detail::codec_not_found_for_type<Arg>();
288  return 0;
289  }
290  }
291 
292  /***/
293  static void decode_and_store_arg(std::byte*& buffer, DynamicFormatArgStore* args_store)
294  {
295  if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>,
296  std::is_same<Arg, void const*>, std::is_same<Arg, void*>>)
297  {
298  args_store->push_back(decode_arg(buffer));
299  }
300  else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>,
301  std::conjunction<std::is_array<Arg>, std::is_same<detail::remove_cvref_t<std::remove_extent_t<Arg>>, char>>>)
302  {
303  // c strings or char array
304  char const* arg = decode_arg(buffer);
305 
306  // pass the string_view to args_store to avoid the dynamic allocation
307  // we pass fmtquill::string_view since fmt/base.h includes a formatter for that type.
308  // for std::string_view we would need fmt/format.h
309  args_store->push_back(fmtquill::string_view{arg, detail::safe_strnlen(arg)});
310  }
311  else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
312  {
313  std::string_view arg = decode_arg(buffer);
314 
315  // pass the string_view to args_store to avoid the dynamic allocation
316  // we pass fmtquill::string_view since fmt/base.h includes a formatter for that type.
317  // for std::string_view we would need fmt/format.h
318  args_store->push_back(fmtquill::string_view{arg.data(), arg.size()});
319  }
320  else
321  {
322  detail::codec_not_found_for_type<Arg>();
323  }
324  }
325 };
326 
327 namespace detail
328 {
336 template <typename... Args>
337 QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t compute_encoded_size_and_cache_string_lengths(
338  QUILL_MAYBE_UNUSED SizeCacheVector& conditional_arg_size_cache, Args const&... args)
339 {
340  if constexpr (!std::conjunction_v<std::disjunction<
341  std::is_arithmetic<remove_cvref_t<Args>>, std::is_enum<remove_cvref_t<Args>>,
342  std::is_same<remove_cvref_t<Args>, void const*>, std::is_same<remove_cvref_t<Args>, void*>,
343  is_std_string<remove_cvref_t<Args>>, std::is_same<remove_cvref_t<Args>, std::string_view>>...>)
344  {
345  // Clear the cache whenever processing involves non-fundamental types,
346  // or when the arguments are not of type std::string or std::string_view.
347  conditional_arg_size_cache.clear();
348  }
349 
350  size_t total_sum{0};
351  // Avoid using a fold expression with '+ ...' because we require a guaranteed evaluation
352  // order to ensure that each argument is processed in sequence. This is essential for
353  // correctly populating the conditional_arg_size_cache
354  ((total_sum += Codec<remove_cvref_t<Args>>::compute_encoded_size(conditional_arg_size_cache, args)), ...);
355  return total_sum;
356 }
357 
364 template <typename... Args>
365 QUILL_ATTRIBUTE_HOT void encode(std::byte*& buffer, SizeCacheVector const& conditional_arg_size_cache,
366  Args const&... args)
367 {
368  QUILL_MAYBE_UNUSED uint32_t conditional_arg_size_cache_index{0};
369  (Codec<remove_cvref_t<Args>>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, args),
370  ...);
371 }
372 
373 template <typename... Args>
374 void decode_and_store_arg(std::byte*& buffer, QUILL_MAYBE_UNUSED DynamicFormatArgStore* args_store)
375 {
376  (Codec<Args>::decode_and_store_arg(buffer, args_store), ...);
377 }
378 
382 using FormatArgsDecoder = void (*)(std::byte*& data, DynamicFormatArgStore& args_store);
383 
384 template <typename... Args>
385 void decode_and_store_args(std::byte*& buffer, DynamicFormatArgStore& args_store)
386 {
387  args_store.clear();
388  decode_and_store_arg<Args...>(buffer, &args_store);
389 }
390 } // namespace detail
391 
393 /***/
394 template <typename... TMembers>
395 size_t compute_total_encoded_size(detail::SizeCacheVector& conditional_arg_size_cache, TMembers const&... members)
396 {
397  size_t total_size{0};
398  ((total_size += Codec<detail::remove_cvref_t<TMembers>>::compute_encoded_size(conditional_arg_size_cache, members)),
399  ...);
400  return total_size;
401 }
402 
403 /***/
404 template <typename... TMembers>
405 void encode_members(std::byte*& buffer, detail::SizeCacheVector const& conditional_arg_size_cache,
406  uint32_t& conditional_arg_size_cache_index, TMembers const&... members)
407 {
408  ((Codec<detail::remove_cvref_t<TMembers>>::encode(buffer, conditional_arg_size_cache,
409  conditional_arg_size_cache_index, members)),
410  ...);
411 }
412 
413 /***/
414 template <typename T, typename... TMembers>
415 void decode_members(std::byte*& buffer, T&, TMembers&... members)
416 {
417  // T& arg is not used but if we remove it, it will crash all users who are passing the extra argument without a compile time error
418  ((members = Codec<detail::remove_cvref_t<TMembers>>::decode_arg(buffer)), ...);
419 }
420 
421 QUILL_END_NAMESPACE
void clear()
Erase all elements from the store.
Definition: DynamicFormatArgStore.h:130
Definition: format.h:126
void(*)(std::byte *&data, DynamicFormatArgStore &args_store) FormatArgsDecoder
Decode functions.
Definition: Codec.h:382
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t compute_encoded_size_and_cache_string_lengths(QUILL_MAYBE_UNUSED SizeCacheVector &conditional_arg_size_cache, Args const &... args)
Calculates the total size required to encode the provided arguments.
Definition: Codec.h:337
Similar to fmt::dynamic_arg_store but better suited to our needs e.g does not include <functional> an...
Definition: DynamicFormatArgStore.h:60
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:24
typename = void for specializations with enable_if
Definition: Codec.h:142
void push_back(T const &arg)
Adds an argument for later passing to a formatting function.
Definition: DynamicFormatArgStore.h:99
QUILL_ATTRIBUTE_HOT void encode(std::byte *&buffer, SizeCacheVector const &conditional_arg_size_cache, Args const &... args)
Encodes multiple arguments into a buffer.
Definition: Codec.h:365
C++14 implementation of C++20&#39;s remove_cvref.
Definition: Codec.h:49
std string detection, ignoring the Allocator type
Definition: Codec.h:130
A contiguous memory buffer with an optional growing ability.
Definition: base.h:1751