1 | // |
2 | // Copyright(c) 2015 Gabi Melman. |
3 | // Distributed under the MIT License (http://opensource.org/licenses/MIT) |
4 | // |
5 | |
6 | #pragma once |
7 | |
8 | #include <cctype> |
9 | #include <spdlog/common.h> |
10 | |
11 | #if defined(__has_include) |
12 | # if __has_include(<version>) |
13 | # include <version> |
14 | # endif |
15 | #endif |
16 | |
17 | #if __cpp_lib_span >= 202002L |
18 | # include <span> |
19 | #endif |
20 | |
21 | // |
22 | // Support for logging binary data as hex |
23 | // format flags, any combination of the following: |
24 | // {:X} - print in uppercase. |
25 | // {:s} - don't separate each byte with space. |
26 | // {:p} - don't print the position on each line start. |
27 | // {:n} - don't split the output to lines. |
28 | // {:a} - show ASCII if :n is not set |
29 | |
30 | // |
31 | // Examples: |
32 | // |
33 | // std::vector<char> v(200, 0x0b); |
34 | // logger->info("Some buffer {}", spdlog::to_hex(v)); |
35 | // char buf[128]; |
36 | // logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf))); |
37 | // logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf), 16)); |
38 | |
39 | namespace spdlog { |
40 | namespace details { |
41 | |
42 | template<typename It> |
43 | class dump_info |
44 | { |
45 | public: |
46 | dump_info(It range_begin, It range_end, size_t size_per_line) |
47 | : begin_(range_begin) |
48 | , end_(range_end) |
49 | , size_per_line_(size_per_line) |
50 | {} |
51 | |
52 | // do not use begin() and end() to avoid collision with fmt/ranges |
53 | It get_begin() const |
54 | { |
55 | return begin_; |
56 | } |
57 | It get_end() const |
58 | { |
59 | return end_; |
60 | } |
61 | size_t size_per_line() const |
62 | { |
63 | return size_per_line_; |
64 | } |
65 | |
66 | private: |
67 | It begin_, end_; |
68 | size_t size_per_line_; |
69 | }; |
70 | } // namespace details |
71 | |
72 | // create a dump_info that wraps the given container |
73 | template<typename Container> |
74 | inline details::dump_info<typename Container::const_iterator> to_hex(const Container &container, size_t size_per_line = 32) |
75 | { |
76 | static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1" ); |
77 | using Iter = typename Container::const_iterator; |
78 | return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line); |
79 | } |
80 | |
81 | #if __cpp_lib_span >= 202002L |
82 | |
83 | template<typename Value, size_t Extent> |
84 | inline details::dump_info<typename std::span<Value, Extent>::iterator> to_hex( |
85 | const std::span<Value, Extent> &container, size_t size_per_line = 32) |
86 | { |
87 | using Container = std::span<Value, Extent>; |
88 | static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1" ); |
89 | using Iter = typename Container::iterator; |
90 | return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line); |
91 | } |
92 | |
93 | #endif |
94 | |
95 | // create dump_info from ranges |
96 | template<typename It> |
97 | inline details::dump_info<It> to_hex(const It range_begin, const It range_end, size_t size_per_line = 32) |
98 | { |
99 | return details::dump_info<It>(range_begin, range_end, size_per_line); |
100 | } |
101 | |
102 | } // namespace spdlog |
103 | |
104 | namespace |
105 | #ifdef SPDLOG_USE_STD_FORMAT |
106 | std |
107 | #else |
108 | fmt |
109 | #endif |
110 | { |
111 | |
112 | template<typename T> |
113 | struct formatter<spdlog::details::dump_info<T>, char> |
114 | { |
115 | const char delimiter = ' '; |
116 | bool put_newlines = true; |
117 | bool put_delimiters = true; |
118 | bool use_uppercase = false; |
119 | bool put_positions = true; // position on start of each line |
120 | bool show_ascii = false; |
121 | |
122 | // parse the format string flags |
123 | template<typename ParseContext> |
124 | SPDLOG_CONSTEXPR_FUNC auto parse(ParseContext &ctx) -> decltype(ctx.begin()) |
125 | { |
126 | auto it = ctx.begin(); |
127 | while (it != ctx.end() && *it != '}') |
128 | { |
129 | switch (*it) |
130 | { |
131 | case 'X': |
132 | use_uppercase = true; |
133 | break; |
134 | case 's': |
135 | put_delimiters = false; |
136 | break; |
137 | case 'p': |
138 | put_positions = false; |
139 | break; |
140 | case 'n': |
141 | put_newlines = false; |
142 | show_ascii = false; |
143 | break; |
144 | case 'a': |
145 | if (put_newlines) |
146 | { |
147 | show_ascii = true; |
148 | } |
149 | break; |
150 | } |
151 | |
152 | ++it; |
153 | } |
154 | return it; |
155 | } |
156 | |
157 | // format the given bytes range as hex |
158 | template<typename FormatContext, typename Container> |
159 | auto format(const spdlog::details::dump_info<Container> &the_range, FormatContext &ctx) -> decltype(ctx.out()) |
160 | { |
161 | SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF" ; |
162 | SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef" ; |
163 | const char *hex_chars = use_uppercase ? hex_upper : hex_lower; |
164 | |
165 | #if !defined(SPDLOG_USE_STD_FORMAT) && FMT_VERSION < 60000 |
166 | auto inserter = ctx.begin(); |
167 | #else |
168 | auto inserter = ctx.out(); |
169 | #endif |
170 | |
171 | int size_per_line = static_cast<int>(the_range.size_per_line()); |
172 | auto start_of_line = the_range.get_begin(); |
173 | for (auto i = the_range.get_begin(); i != the_range.get_end(); i++) |
174 | { |
175 | auto ch = static_cast<unsigned char>(*i); |
176 | |
177 | if (put_newlines && (i == the_range.get_begin() || i - start_of_line >= size_per_line)) |
178 | { |
179 | if (show_ascii && i != the_range.get_begin()) |
180 | { |
181 | *inserter++ = delimiter; |
182 | *inserter++ = delimiter; |
183 | for (auto j = start_of_line; j < i; j++) |
184 | { |
185 | auto pc = static_cast<unsigned char>(*j); |
186 | *inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.'; |
187 | } |
188 | } |
189 | |
190 | put_newline(inserter, static_cast<size_t>(i - the_range.get_begin())); |
191 | |
192 | // put first byte without delimiter in front of it |
193 | *inserter++ = hex_chars[(ch >> 4) & 0x0f]; |
194 | *inserter++ = hex_chars[ch & 0x0f]; |
195 | start_of_line = i; |
196 | continue; |
197 | } |
198 | |
199 | if (put_delimiters) |
200 | { |
201 | *inserter++ = delimiter; |
202 | } |
203 | |
204 | *inserter++ = hex_chars[(ch >> 4) & 0x0f]; |
205 | *inserter++ = hex_chars[ch & 0x0f]; |
206 | } |
207 | if (show_ascii) // add ascii to last line |
208 | { |
209 | if (the_range.get_end() - the_range.get_begin() > size_per_line) |
210 | { |
211 | auto blank_num = size_per_line - (the_range.get_end() - start_of_line); |
212 | while (blank_num-- > 0) |
213 | { |
214 | *inserter++ = delimiter; |
215 | *inserter++ = delimiter; |
216 | if (put_delimiters) |
217 | { |
218 | *inserter++ = delimiter; |
219 | } |
220 | } |
221 | } |
222 | *inserter++ = delimiter; |
223 | *inserter++ = delimiter; |
224 | for (auto j = start_of_line; j != the_range.get_end(); j++) |
225 | { |
226 | auto pc = static_cast<unsigned char>(*j); |
227 | *inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.'; |
228 | } |
229 | } |
230 | return inserter; |
231 | } |
232 | |
233 | // put newline(and position header) |
234 | template<typename It> |
235 | void put_newline(It inserter, std::size_t pos) |
236 | { |
237 | #ifdef _WIN32 |
238 | *inserter++ = '\r'; |
239 | #endif |
240 | *inserter++ = '\n'; |
241 | |
242 | if (put_positions) |
243 | { |
244 | spdlog::fmt_lib::format_to(inserter, SPDLOG_FMT_STRING("{:04X}: " ), pos); |
245 | } |
246 | } |
247 | }; |
248 | } // namespace std |
249 | |