目录
1.引言
在平时调试网络程序时,多数都会用到wireshark抓包工具,在查看某个包的数据都是按照下面这样格式显示的:
那么它是怎么做到的呢?其实spdlog的to_hex也能做到这一点,下面就来介绍它的用法和原理。
2.spdlog::to_hex用法
spdlog::to_hex
这个方法是用于将个字节序列转换为十六进制字符串的功能。它自带的几种十六进制显示格式符号:
符号 | 含义 | 例子 |
{:X} | 以大写字母打印十六进制数,默认是小写显示 | 0000: 09 0A 0B 0C FF FF |
{:s} | 不带间隔符显示 | 0000: 090a0b0cffff |
{:n} | 不要将输出分割成行 | 09 0a 0b 41 0d 4b ff ff |
{:a} | 如果未设置 :n ,则显示 ASCII 字符(对于可打印的 ASCII 字符) | 0000: 09 0a 0b 41 0d 4b ff ff ...A.K.. |
{:p} | 不要在每行开始时打印位置 | 09 0a 0b 41 0d 4b ff ff |
此外,你还可以指定每行显示的字节数,例如,通过 {:Xspn}
这样的格式化字符串,to_hex的最后一个参数的 32 表示每行显示 32个字节。这些选项可以混合使用,以满足不同的输出需求。
其次在使用的spdlog::to_hex的时候,内容必须是字节流或字符串。
下面是几个示例:
void testLog()
{
std::vector<unsigned char> v{ 9, 0xa, 0xb, 0xc, 0xff, 0xff };
UVLOG_INFO("1: {}", spdlog::to_hex(v)); //普通十六进制输出: 0000: 09 0a 0b 0c ff ff
std::vector<unsigned char> v1{ 9, 0xa, 0xb, 0xc, 0xff, 0xff };
UVLOG_INFO("2: {:X}", spdlog::to_hex(v1)); //十六进制大写输出 : 0000: 09 0A 0B 0C FF FF
UVLOG_INFO("21: {:s}", spdlog::to_hex(v1)); //十六进制大写输出 : 0000: 090a0b0cffff
std::vector<unsigned char> v2{ 9, 0xa, 0xb, 0xc, 0xff, 0xff };
UVLOG_INFO("3: {:sX}", spdlog::to_hex(v2)); //十六进制不间隔输出:0000: 090A0B0CFFFF
std::vector<unsigned char> v3{ 9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff };
UVLOG_INFO("4: {:Xsa}", spdlog::to_hex(v3, 8));//十六进制不间隔大写带ascii输出:0000: 090A0B410C4BFFFF ...A.K..
UVLOG_INFO("41: {:a}", spdlog::to_hex(v3, 8));//十六进制带ascii输出:0000: 09 0a 0b 41 0c 4b ff ff ...A.K..
UVLOG_INFO("42: {:na}", spdlog::to_hex(v3, 8));//十六进制不带ascii输出:09 0a 0b 41 0c 4b ff ff
std::vector<unsigned char> v4{ 9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff };
UVLOG_INFO("5: {:Xsa}", spdlog::to_hex(v4, 10)); //0000: 090A0B410C4BFFFF ...A.K..
UVLOG_INFO("6: {:Xs}", spdlog::to_hex(v4, 10)); //0000: 090A0B410C4BFFFF
UVLOG_INFO("7: {:Xsa}", spdlog::to_hex(v4, 6)); //0000: 090A0B410C4B ...A.K
//0006: FFFF
UVLOG_INFO("8: {:Xs}", spdlog::to_hex(v4, 6)); //十六进制大写不间隔并且每行6个字节输出:0000: 090A0B410C4B
// 0006: FFFF
std::vector<unsigned char> v5{ 9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff };
UVLOG_INFO("9: {:Xs}", spdlog::to_hex(v5, 8)); //十六进制大写不间隔输出:0000: 090A0B410C4BFFFF
UVLOG_INFO("10: {:Xsna}", spdlog::to_hex(v5, 8)); //十六进制大写不间隔并且不用ascii码显示:090A0B410C4BFFFF
UVLOG_INFO("11: {:Xp}", spdlog::to_hex(v5, 8)); //十六进制大写并且不带长度提示:09 0A 0B 41 0C 4B FF FF
}
spdlog::to_hex也可以接受字符数组或字符指针,如:
void printHexToLog(const char* s, int len) {
UVLOG_INFO("21: {}", spdlog::to_hex(std::string_view(s, len))); //建议使用
UVLOG_INFO("22: {}", spdlog::to_hex(std::initializer_list(s, s + len))); //建议使用
UVLOG_INFO("23: {}", spdlog::to_hex(std::span(s, len))); //建议使用
UVLOG_INFO("24: {}", spdlog::to_hex(std::string(s, len)));
UVLOG_INFO("25: {}", spdlog::to_hex(s, s + len)); //建议使用
}
void testLog()
{
char ss[] = "12345678888888889";
//[1]
UVLOG_INFO("31: {}", spdlog::to_hex(std::span(ss, sizeof(ss)))); //建议使用
UVLOG_INFO("32: {}", spdlog::to_hex(ss, ss + sizeof(ss))); //建议使用
UVLOG_INFO("33: {}", spdlog::to_hex(std::begin(ss), std::end(ss))); //建议使用
//[2]
printHexToLog(ss, sizeof(ss));
}
输出:
2024-11-09 22:21:38.400389 <thread 24096> [info] 31:
0000: 31 32 33 34 35 36 37 38 38 38 38 38 38 38 38 38 39 00
2024-11-09 22:21:38.400401 <thread 24096> [info] 32:
0000: 31 32 33 34 35 36 37 38 38 38 38 38 38 38 38 38 39 00
2024-11-09 22:21:38.400411 <thread 24096> [info] 33:
0000: 31 32 33 34 35 36 37 38 38 38 38 38 38 38 38 38 39 00
2024-11-09 22:21:38.400430 <thread 24096> [info] 21:
0000: 31 32 33 34 35 36 37 38 38 38 38 38 38 38 38 38 39 00
2024-11-09 22:21:38.400440 <thread 24096> [info] 22:
0000: 31 32 33 34 35 36 37 38 38 38 38 38 38 38 38 38 39 00
2024-11-09 22:21:38.400452 <thread 24096> [info] 23:
0000: 31 32 33 34 35 36 37 38 38 38 38 38 38 38 38 38 39 00
2024-11-09 22:21:38.400470 <thread 24096> [info] 24:
0000: 31 32 33 34 35 36 37 38 38 38 38 38 38 38 38 38 39 00
2024-11-09 22:21:38.400483 <thread 24096> [info] 25:
0000: 31 32 33 34 35 36 37 38 38 38 38 38 38 38 38 38 39 00
3.spdlog::to_hex实现原理
查看spdlog的源码目录,在.\spdlog\include\spdlog\fmt\bin_to_hex.h里面找到了spdlog::to_hex的实现:
1)迭代器版本
// create dump_info from ranges
template<typename It>
inline details::dump_info<It> to_hex(const It range_begin, const It range_end, size_t size_per_line = 32)
{
return details::dump_info<It>(range_begin, range_end, size_per_line);
}
这个迭代器包括了C++所有容器迭代器和原生数组指针。上面代码中的
UVLOG_INFO("32: {}", spdlog::to_hex(ss, ss + sizeof(ss))); //建议使用
UVLOG_INFO("33: {}", spdlog::to_hex(std::begin(ss), std::end(ss))); //建议使用
都会调用此版本。
2) 容器版本
template<typename Container>
inline details::dump_info<typename Container::const_iterator> to_hex(const Container &container, size_t size_per_line = 32)
{
static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1");
using Iter = typename Container::const_iterator;
return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line);
}
当to_hex传入的容器(其中包括C++所有容器)并且容器中真实数据类型长度只能为1:
static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1");
最后还是调用迭代器版本完成日志输出。
3)std::span版本
在C++20开始,C++标准支持了std::span,它就可以直接支持原生字符数组或字符指针加长度的形式:
#if __cpp_lib_span >= 202002L
template<typename Value, size_t Extent>
inline details::dump_info<typename std::span<Value, Extent>::iterator> to_hex(
const std::span<Value, Extent> &container, size_t size_per_line = 32)
{
using Container = std::span<Value, Extent>;
static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1");
using Iter = typename Container::iterator;
return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line);
}
#endif
最后还是调用迭代器版本完成日志输出。
不管什么版本,to_hex函数都是返回dump_info:
template<typename It>
class dump_info
{
public:
dump_info(It range_begin, It range_end, size_t size_per_line)
: begin_(range_begin)
, end_(range_end)
, size_per_line_(size_per_line)
{}
// do not use begin() and end() to avoid collision with fmt/ranges
It get_begin() const
{
return begin_;
}
It get_end() const
{
return end_;
}
size_t size_per_line() const
{
return size_per_line_;
}
private:
It begin_, end_;
size_t size_per_line_;
};
于是写了特化dump_info版的formatter:
template<typename T>
struct formatter<spdlog::details::dump_info<T>, char>
{
const char delimiter = ' ';
bool put_newlines = true;
bool put_delimiters = true;
bool use_uppercase = false;
bool put_positions = true; // position on start of each line
bool show_ascii = false;
// parse the format string flags
template<typename ParseContext>
SPDLOG_CONSTEXPR_FUNC auto parse(ParseContext &ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin();
while (it != ctx.end() && *it != '}')
{
switch (*it)
{
case 'X':
use_uppercase = true;
break;
case 's':
put_delimiters = false;
break;
case 'p':
put_positions = false;
break;
case 'n':
put_newlines = false;
show_ascii = false;
break;
case 'a':
if (put_newlines)
{
show_ascii = true;
}
break;
}
++it;
}
return it;
}
// format the given bytes range as hex
template<typename FormatContext, typename Container>
auto format(const spdlog::details::dump_info<Container> &the_range, FormatContext &ctx) -> decltype(ctx.out())
{
SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF";
SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef";
const char *hex_chars = use_uppercase ? hex_upper : hex_lower;
#if !defined(SPDLOG_USE_STD_FORMAT) && FMT_VERSION < 60000
auto inserter = ctx.begin();
#else
auto inserter = ctx.out();
#endif
int size_per_line = static_cast<int>(the_range.size_per_line());
auto start_of_line = the_range.get_begin();
for (auto i = the_range.get_begin(); i != the_range.get_end(); i++)
{
auto ch = static_cast<unsigned char>(*i);
if (put_newlines && (i == the_range.get_begin() || i - start_of_line >= size_per_line))
{
if (show_ascii && i != the_range.get_begin())
{
*inserter++ = delimiter;
*inserter++ = delimiter;
for (auto j = start_of_line; j < i; j++)
{
auto pc = static_cast<unsigned char>(*j);
*inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.';
}
}
put_newline(inserter, static_cast<size_t>(i - the_range.get_begin()));
// put first byte without delimiter in front of it
*inserter++ = hex_chars[(ch >> 4) & 0x0f];
*inserter++ = hex_chars[ch & 0x0f];
start_of_line = i;
continue;
}
if (put_delimiters)
{
*inserter++ = delimiter;
}
*inserter++ = hex_chars[(ch >> 4) & 0x0f];
*inserter++ = hex_chars[ch & 0x0f];
}
if (show_ascii) // add ascii to last line
{
if (the_range.get_end() - the_range.get_begin() > size_per_line)
{
auto blank_num = size_per_line - (the_range.get_end() - start_of_line);
while (blank_num-- > 0)
{
*inserter++ = delimiter;
*inserter++ = delimiter;
if (put_delimiters)
{
*inserter++ = delimiter;
}
}
}
*inserter++ = delimiter;
*inserter++ = delimiter;
for (auto j = start_of_line; j != the_range.get_end(); j++)
{
auto pc = static_cast<unsigned char>(*j);
*inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.';
}
}
return inserter;
}
// put newline(and position header)
template<typename It>
void put_newline(It inserter, std::size_t pos)
{
#ifdef _WIN32
*inserter++ = '\r';
#endif
*inserter++ = '\n';
if (put_positions)
{
spdlog::fmt_lib::format_to(inserter, SPDLOG_FMT_STRING("{:04X}: "), pos);
}
}
};
在上面代码中也可以看到前面讲到的格式符号{:X}、{:a}、{:n}、{:p}、{:s}的实现,代码不是很复杂,在这里就不一一赘述了。
4.总结
在众多的日志输出库中,我感觉使用起来spdlog::to_hex输出十六进制是最好用的,这跟spdlog高效易用是相关的。强烈推荐大家使用。
标签:std,十六进制,38,hex,spdlog,line,size From: https://blog.csdn.net/haokan123456789/article/details/143651558