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
39namespace spdlog {
40namespace details {
41
42template<typename It>
43class dump_info
44{
45public:
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
66private:
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
73template<typename Container>
74inline 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
83template<typename Value, size_t Extent>
84inline 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
96template<typename It>
97inline 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
104namespace
105#ifdef SPDLOG_USE_STD_FORMAT
106 std
107#else
108 fmt
109#endif
110{
111
112template<typename T>
113struct 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