Formatter

Formatter负责将日志格式化为字符串。Spdlog提供了多种Formatter,比如pattern_formatter(按指定的格式输出日志)、json_formatter(以JSON格式输出日志)等。Formatter的定义如下:

1
2
3
4
5
6
7
class formatter
{
public:
virtual ~formatter() = default;
virtual void format(const details::log_msg &msg, memory_buf_t &dest) = 0;
virtual std::unique_ptr<formatter> clone() const = 0;
};

formatter 是一个抽象类,它包含一个format()方法用于将日志消息格式化为字符串。不同的Formatter实现会按照不同的格式输出日志。

实际在使用的是 pattern_formatter (继承自formatter),在base_sink的默认构造函数里为我们初始化好了。

1
2
3
4
template<typename Mutex>
SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink()
: formatter_{details::make_unique<spdlog::pattern_formatter>()}
{}

pattern_formatter

1
2
3
4
5
6
7
8
9
10
11
12
13
class SPDLOG_API pattern_formatter final : public formatter
{
public:
using custom_flags = std::unordered_map<char, std::unique_ptr<custom_flag_formatter>>;

explicit pattern_formatter(std::string pattern, pattern_time_type time_type = pattern_time_type::local,
std::string eol = spdlog::details::os::default_eol, custom_flags custom_user_flags = custom_flags());
// ...

private:
// ...
std::vector<std::unique_ptr<details::flag_formatter>> formatters_;
};

pattern_formatter 里提供了一组格式化器 formatters_ 每个格式化器都继承基类 flag_formatter 实现特定的格式化功能。spdlog 提供的格式化器主要有:

  • level_formatter(输出日志级别)
  • short_level_formatter(输出日志级别的缩写)
  • datetime_formatter(输出日期和时间)
  • message_formatter(输出日志消息)
  • color_start_formatter和color_stop_formatter(输出带颜色的日志) -源代码位置
  • name_formatter (输出日志器名称)
  • color_start_bold_formatter 和 color_stop_formatter(输出带颜色和加粗的日志)
  • source_location_formatter (获取源文件地址)
  • thread_id_formatter(输出线程ID)
  • process_id_formatter(输出进程ID)
  • file_formatter(输出源代码文件名)
  • line_formatter(输出源代码行号)

通过继承 flag_formatter 也可以实现自定义的格式化器,带来更多的灵活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
class SPDLOG_API flag_formatter
{
public:
explicit flag_formatter(padding_info padinfo)
: padinfo_(padinfo)
{}
flag_formatter() = default;
virtual ~flag_formatter() = default;
virtual void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) = 0;

protected:
padding_info padinfo_;
};

在调用设置格式化字符串函数 set_pattern的时候,pattern_formatter 会解析字符串,将字符串转换成不同的格式化器添加到 formatters_ 里面。具体的逻辑在 compile_pattern_ 函数实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
SPDLOG_INLINE void pattern_formatter::compile_pattern_(const std::string &pattern)
{
auto end = pattern.end();
std::unique_ptr<details::aggregate_formatter> user_chars;
formatters_.clear();
for (auto it = pattern.begin(); it != end; ++it)
{
if (*it == '%')
{
if (user_chars) // append user chars found so far
{
formatters_.push_back(std::move(user_chars));
}

auto padding = handle_padspec_(++it, end);

if (it != end)
{
if (padding.enabled())
{
handle_flag_<details::scoped_padder>(*it, padding);
}
else
{
handle_flag_<details::null_scoped_padder>(*it, padding);
}
}
else
{
break;
}
}
else // chars not following the % sign should be displayed as is
{
if (!user_chars)
{
user_chars = details::make_unique<details::aggregate_formatter>();
}
user_chars->add_ch(*it);
}
}
if (user_chars) // append raw chars found so far
{
formatters_.push_back(std::move(user_chars));
}
}

代码的核心原理就是遍历字符串解析成指定的格式化器。

sink写日志的时候,调用格式化器格式化,如 basic_file_sink

1
2
3
4
5
6
7
template<typename Mutex>
SPDLOG_INLINE void basic_file_sink<Mutex>::sink_it_(const details::log_msg &msg)
{
memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted);
file_helper_.write(formatted);
}

不过这里有个疑惑:format 格式化为什么写在 base_sink 基类里呢?,这样省得每个派生的sink都要写一遍 format 格式化代码。

参考链接:https://zhuanlan.zhihu.com/p/620251465