| 1 | // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. |
| 2 | // Distributed under the MIT License (http://opensource.org/licenses/MIT) |
| 3 | #pragma once |
| 4 | |
| 5 | #include <chrono> |
| 6 | #include <iterator> |
| 7 | #include <spdlog/common.h> |
| 8 | #include <spdlog/fmt/fmt.h> |
| 9 | #include <type_traits> |
| 10 | |
| 11 | #ifdef SPDLOG_USE_STD_FORMAT |
| 12 | #include <charconv> |
| 13 | #include <limits> |
| 14 | #endif |
| 15 | |
| 16 | // Some fmt helpers to efficiently format and pad ints and strings |
| 17 | namespace spdlog { |
| 18 | namespace details { |
| 19 | namespace fmt_helper { |
| 20 | |
| 21 | inline void append_string_view(spdlog::string_view_t view, memory_buf_t &dest) { |
| 22 | auto *buf_ptr = view.data(); |
| 23 | dest.append(first: buf_ptr, last: buf_ptr + view.size()); |
| 24 | } |
| 25 | |
| 26 | #ifdef SPDLOG_USE_STD_FORMAT |
| 27 | template <typename T> |
| 28 | inline void append_int(T n, memory_buf_t &dest) { |
| 29 | // Buffer should be large enough to hold all digits (digits10 + 1) and a sign |
| 30 | SPDLOG_CONSTEXPR const auto BUF_SIZE = std::numeric_limits<T>::digits10 + 2; |
| 31 | char buf[BUF_SIZE]; |
| 32 | |
| 33 | auto [ptr, ec] = std::to_chars(buf, buf + BUF_SIZE, n, 10); |
| 34 | if (ec == std::errc()) { |
| 35 | dest.append(buf, ptr); |
| 36 | } else { |
| 37 | throw_spdlog_ex(msg: "Failed to format int" , last_errno: static_cast<int>(ec)); |
| 38 | } |
| 39 | } |
| 40 | #else |
| 41 | template <typename T> |
| 42 | inline void append_int(T n, memory_buf_t &dest) { |
| 43 | fmt::format_int i(n); |
| 44 | dest.append(i.data(), i.data() + i.size()); |
| 45 | } |
| 46 | #endif |
| 47 | |
| 48 | template <typename T> |
| 49 | SPDLOG_CONSTEXPR_FUNC unsigned int count_digits_fallback(T n) { |
| 50 | // taken from fmt: https://github.com/fmtlib/fmt/blob/8.0.1/include/fmt/format.h#L899-L912 |
| 51 | unsigned int count = 1; |
| 52 | for (;;) { |
| 53 | // Integer division is slow so do it for a group of four digits instead |
| 54 | // of for every digit. The idea comes from the talk by Alexandrescu |
| 55 | // "Three Optimization Tips for C++". See speed-test for a comparison. |
| 56 | if (n < 10) return count; |
| 57 | if (n < 100) return count + 1; |
| 58 | if (n < 1000) return count + 2; |
| 59 | if (n < 10000) return count + 3; |
| 60 | n /= 10000u; |
| 61 | count += 4; |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | template <typename T> |
| 66 | inline unsigned int count_digits(T n) { |
| 67 | using count_type = |
| 68 | typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type; |
| 69 | #ifdef SPDLOG_USE_STD_FORMAT |
| 70 | return count_digits_fallback(static_cast<count_type>(n)); |
| 71 | #else |
| 72 | return static_cast<unsigned int>(fmt:: |
| 73 | // fmt 7.0.0 renamed the internal namespace to detail. |
| 74 | // See: https://github.com/fmtlib/fmt/issues/1538 |
| 75 | #if FMT_VERSION < 70000 |
| 76 | internal |
| 77 | #else |
| 78 | detail |
| 79 | #endif |
| 80 | ::count_digits(static_cast<count_type>(n))); |
| 81 | #endif |
| 82 | } |
| 83 | |
| 84 | inline void pad2(int n, memory_buf_t &dest) { |
| 85 | if (n >= 0 && n < 100) // 0-99 |
| 86 | { |
| 87 | dest.push_back(c: static_cast<char>('0' + n / 10)); |
| 88 | dest.push_back(c: static_cast<char>('0' + n % 10)); |
| 89 | } else // unlikely, but just in case, let fmt deal with it |
| 90 | { |
| 91 | fmt_lib::format_to(out: std::back_inserter(x&: dest), SPDLOG_FMT_STRING("{:02}" ), args&: n); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | template <typename T> |
| 96 | inline void pad_uint(T n, unsigned int width, memory_buf_t &dest) { |
| 97 | static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T" ); |
| 98 | for (auto digits = count_digits(n); digits < width; digits++) { |
| 99 | dest.push_back(c: '0'); |
| 100 | } |
| 101 | append_int(n, dest); |
| 102 | } |
| 103 | |
| 104 | template <typename T> |
| 105 | inline void pad3(T n, memory_buf_t &dest) { |
| 106 | static_assert(std::is_unsigned<T>::value, "pad3 must get unsigned T" ); |
| 107 | if (n < 1000) { |
| 108 | dest.push_back(c: static_cast<char>(n / 100 + '0')); |
| 109 | n = n % 100; |
| 110 | dest.push_back(c: static_cast<char>((n / 10) + '0')); |
| 111 | dest.push_back(c: static_cast<char>((n % 10) + '0')); |
| 112 | } else { |
| 113 | append_int(n, dest); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | template <typename T> |
| 118 | inline void pad6(T n, memory_buf_t &dest) { |
| 119 | pad_uint(n, 6, dest); |
| 120 | } |
| 121 | |
| 122 | template <typename T> |
| 123 | inline void pad9(T n, memory_buf_t &dest) { |
| 124 | pad_uint(n, 9, dest); |
| 125 | } |
| 126 | |
| 127 | // return fraction of a second of the given time_point. |
| 128 | // e.g. |
| 129 | // fraction<std::milliseconds>(tp) -> will return the millis part of the second |
| 130 | template <typename ToDuration> |
| 131 | inline ToDuration time_fraction(log_clock::time_point tp) { |
| 132 | using std::chrono::duration_cast; |
| 133 | using std::chrono::seconds; |
| 134 | auto duration = tp.time_since_epoch(); |
| 135 | auto secs = duration_cast<seconds>(duration); |
| 136 | return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs); |
| 137 | } |
| 138 | |
| 139 | } // namespace fmt_helper |
| 140 | } // namespace details |
| 141 | } // namespace spdlog |
| 142 | |