模块概述

模仿log4j日志框架实现一个的日志模块

  • 难点:Logformatter的init(),引入了一个简单的状态机机制,实现了将一个字符串解析为模板
  • 设计点:
    • LogEventWrap:一个封装LogEvent和Logger的RAII类
    • LoggerManager:一个管理Logger的单例类
    • LogAppender:一个抽象基类,可以方便地扩展日志的输出地
    • 一系列辅助函数
      • 优化用户使用方式,用户可使用流式方式或格式化方式进行日志内容的写入
      • 包括一系列宏函数和inline,辅助函数创建LogEventWrap来实现日志的输出

模块UML类图

日志模块类图

Logger:日志器

  • 负责进行日志输出
  • 一个日志器可指定一个日志级别(LogLevel)和多个日志输出地(LogAppender)
  • 提供log方法,传入日志事件,如果该日志事件的级别高于日志器本身的级别则将其输出,否则将该日志抛弃。

LogAppender:日志输出器

  • 用于将一个日志事件输出到对应的输出地。
  • 类内部包含一个日志格式器(LogFormatter)成员和一个log方法,日志事件经过格式化后输出到对应的输出地。
  • 由于有多种输出地,所以该类成为一个抽象基类,其派生类必须重写它的log方法
    • 比如StdoutLogAppender和FileLogAppender为LogAppender的派生类,分别表示输出到终端和文件。

LogFormatter:日志格式器

  • 与log4cpp的PatternLayout对应,用于格式化一个日志事件(输出log4j日志格式)。
  • 该类构建时可以指定pattern,表示如何进行格式化。提供format方法,用于将日志事件格式化成字符串。
    • pattern中每个模板参数或字符串对应一个FormatItem派生类

LogEvent:日志事件

  • 用于记录日志现场,比如该日志的级别,文件名/行号,日志消息,线程/协程号,所属日志器名称等。
  • 可以理解为一个日志事件对应一条日志

LogEventWrap:日志事件包装类

  • LogEventWrap是一个RAII类
    • LogEventWrap在构造时指定日志事件和日志器
    • 在析构时调用日志器的log方法将日志事件进行输出
  • LogEventWrap将日志事件和日志器包装到一起
    • 一条日志只会在一个日志器上进行输出
    • 将日志事件和日志器包装到一起后,方便通过宏定义来简化日志模块的使用。
  • 为什么需要LogEventWrap?
    • 由于LogEvent::ptr是智能指针,所以如果直接创建LogEvent::ptr,智能指针将在主函数结束时才释放。
    • LogEventWrap的getSS()方法支持以流式方式将日志写入logger
    • 使用RAII类管理LogEvent,可以在LogEventWrap析构时自动提交日志事件,并将其释放

LogManager:日志器管理类

  • 单例模式,用于统一管理所有的日志器,提供日志器的创建与获取方法
    • LogManager应成为创建Logger的唯一方式,所有的Logger都应该经由LogManager获取
    • 通过singleton.h中定义的单例类GetInstance方法返回LoggerManager的单例对象
  • getRoot方法:LogManager自带一个root Logger,用于为日志模块提供一个初始可用的日志器。
  • getLogger方法:根据一个名字搜索容器中已有的logger或新建一个logger

调用栈

~LogEventWrap()Logger::log()LogAppender::log()LogFormatter::format()

代码解析

LogLevel

LogLevel表示日志级别

  • 类中定义了7种日志级别,实现了日志的分级输出
  • 提供了ToStringFromString两个静态方法,支持将日志级别名称转换为字符串,或将字符串转换为日志级别
    • FromString对输入字符串的大小写不敏感
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// .h

// 日志级别
class LogLevel{
public:
enum Level{
UNKNOW = 0, // 未知级别
DEBUG = 1, // DEBUG级别
INFO = 2, // INFO级别
WARN = 3, // WARN级别
ERROR = 4, // ERROR级别
FATAL = 5, // FATAL级别
NOTSET = 100 // NOSET级别,表示不可访问
};

static const char* ToString(Level level);

static Level FromString(const std::string& str);
};

通过宏函数简化了ToStringFromString的书写

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
// .cpp

const char* LogLevel::ToString(Level level){
switch (level){
#define XX(name) case LogLevel::name: return #name;
XX(DEBUG);
XX(INFO);
XX(WARN);
XX(ERROR);
XX(FATAL);
#undef XX
default:
return "UNKNOW";
}
}

LogLevel::Level LogLevel::FromString(const std::string& str){
std::string strlower(str.size(), '\0');
std::transform(str.begin(), str.end(), strlower.begin(), ::tolower);
#define XX(level, v) if(strlower == #v){ return LogLevel::level; }
XX(DEBUG, debug);
XX(INFO, info);
XX(WARN, warn);
XX(ERROR, error);
XX(FATAL, fatal);
#undef XX
return UNKNOW;
}

LogEvent

LogEvent用于记录日志现场

一个日志事件具体包括以下内容:

  • 日志内容
  • 日志器名称
  • 日志级别
  • 文件名,对应__FILE__宏
  • 行号,对应__LINE__宏
  • 程序运行时间,通过sylar::GetElapsedMS()获取
  • 线程ID
  • 协程ID
  • UTC时间戳,对应time(0)
  • 线程名称

LogEvent提供了format方法,将以上信息格式化写入日志内容

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
47
48
49
50
51
52
53
54
55
56
57
58
59
// .h

// 日志事件,用于记录日志现场
class LogEvent{
public:
using ptr = std::shared_ptr<LogEvent>;

LogEvent(std::string loggerName, LogLevel::Level level, const char *file, int32_t line
, int64_t elapse, uint32_t thread_id, uint64_t fiber_id, time_t time
, const std::string &thread_name);

std::string getFile() const { return m_file; }

int32_t getLine() const { return m_line; }

uint32_t getElapse() const { return m_elapse; }

uint32_t getThreadId() const { return m_threadId; }

uint32_t getFiberId() const { return m_fiberId; }

uint64_t getTime() const { return m_time; }

const std::string& getThreadName() const { return m_threadName; }

std::string getContent() const { return m_ss.str(); }

std::stringstream& getSS() { return m_ss; }

std::string getLoggerName() const { return m_loggerName; }

LogLevel::Level getLevel() const { return m_level; }

// 以格式化方式将内容写入m_ss
template <typename... Args>
void format(const char* fmt, Args&&... args){
// 获取格式化字符串的长度
int len = snprintf(nullptr, 0, fmt, args...);
if(len >= 0){
// 调用snprintf,将格式化字符串写入缓冲区
char* buf = new char[len + 1];
snprintf(buf, len + 1, fmt, args...);
m_ss << std::string(buf, len);
delete[] buf;
}
}

private:
std::string m_loggerName; // 日志器
LogLevel::Level m_level; // 日志级别
const char* m_file = nullptr; // 文件名
int32_t m_line = 0; // 行号
uint32_t m_elapse = 0; // 程序启动到现在的毫秒数
uint32_t m_threadId = 0; // 线程ID
uint32_t m_fiberId = 0; // 协程ID
uint64_t m_time = 0; // 时间戳
std::string m_threadName; // 线程名称
std::stringstream m_ss; // 日志内容流
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .cpp

LogEvent::LogEvent(std::string loggerName, LogLevel::Level level, const char *file, int32_t line
, int64_t elapse, uint32_t thread_id, uint64_t fiber_id, time_t time
, const std::string &thread_name)
: m_loggerName(loggerName)
, m_level(level)
, m_file(file)
, m_line(line)
, m_elapse(elapse)
, m_threadId(thread_id)
, m_fiberId(fiber_id)
, m_time(time)
, m_threadName(thread_name) {
}

LogFormatter

LogFormatter用于将日志事件LogEvent格式化(模仿log4j日志框架)

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
// .h

// 日志格式器,将日志事件格式化为字符串
class LogFormatter{
public:
using ptr = std::shared_ptr<LogFormatter>;

LogFormatter(const std::string& pattern = "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n");

std::ostream& format(std::ostream& os, LogEvent::ptr event);

bool isError() const { return m_error; }

std::string getPattern() const { return m_pattern; }

private:
void init();

public:
// 抽象基类FormatItem,用于格式化某一项日志内容
class FormatItem{
public:
using ptr = std::shared_ptr<FormatItem>;

virtual ~FormatItem(){};

virtual void format(std::ostream& os, LogEvent::ptr event) = 0;
};

private:
std::string m_pattern;
std::vector<FormatItem::ptr> m_items;
bool m_error;
};

pattern

由于一个日志事件包括了很多的内容(参考LogEvent),但实际上用户并不希望每次输出日志时都将这些信息全部进行输出,而是希望可以自由地选择要输出的信息。并且,用户还可能需要在每条日志里增加一些指定的字符,比如在文件名和行号之间加上一个冒号的情况。

为了实现这项功能,LogFormatter使用了一个模板字符串来指定格式化的方式。模板字符串由普通字符和转义字符构成,转义字符以%开头,比如%m,%p等。除了转义字符,剩下的全部都是普通字符,包括空格,当前支持以下转义字符:

text
1
2
3
4
5
6
7
8
9
10
11
12
%m 消息
%p 日志级别
%r 累计毫秒数
%c 日志名称
%t 线程id
%n 换行
%d 时间
%f 文件名
%l 行号
%T 制表符
%F 协程id
%N 线程名称

默认格式

  • 默认格式: "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
  • 描述:年-月-日 时:分:秒 [累计运行毫秒数] \t 线程id \t 线程名称 \t 协程id \t [日志级别] \t [日志器名称] \t 文件名:行号 \t 日志消息 换行符

嵌套类FormatItem

  • FormatItem:日志内容格式化项,一个抽象基类,每个格式模板参数实现一个FormatItem的派生类

    • void format(std::ostream os, LogEvent::ptr event):将event中对应模板参数的内容格式化后写入os
  • 具体的派生类如下:

    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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    // .cpp

    // %m
    class MessageFormatItem: public LogFormatter::FormatItem{
    public:
    MessageFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << event->getContent();
    }
    };

    // %p
    class LevelFormatItem: public LogFormatter::FormatItem{
    public:
    LevelFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << LogLevel::ToString(event->getLevel());
    }
    };

    // %c
    class LoggerNameFormatItem: public LogFormatter::FormatItem{
    public:
    LoggerNameFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << event->getLoggerName();
    }
    };

    // %d
    class DateTimeFormatItem: public LogFormatter::FormatItem{
    public:
    DateTimeFormatItem(const std::string& format = "%Y-%m-%d %H:%M:%S")
    : m_format(format){
    if(m_format.empty()){
    m_format = "%Y-%m-%d %H:%M:%S";
    }
    }

    void format(std::ostream& os, LogEvent::ptr event) override {
    time_t time = event->getTime();
    struct tm* tm = localtime(&time);
    char buf[64];
    strftime(buf, sizeof(buf), m_format.c_str(), tm);
    os << buf;
    }

    private:
    std::string m_format;
    };

    // %r
    class ElapseFormatItem: public LogFormatter::FormatItem{
    public:
    ElapseFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << event->getElapse();
    }
    };

    // %f
    class FilenameFormatItem: public LogFormatter::FormatItem{
    public:
    FilenameFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << event->getFile();
    }
    };

    // %l
    class LineFormatItem: public LogFormatter::FormatItem{
    public:
    LineFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << event->getLine();
    }
    };

    // %t
    class ThreadIdFormatItem: public LogFormatter::FormatItem{
    public:
    ThreadIdFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << event->getThreadId();
    }
    };

    // %f
    class FiberIdFormatItem: public LogFormatter::FormatItem{
    public:
    FiberIdFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << event->getFiberId();
    }
    };

    // %N
    class ThreadNameFormatItem: public LogFormatter::FormatItem{
    public:
    ThreadNameFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << event->getThreadName();
    }
    };

    // %%
    class PercentSignFormatItem: public LogFormatter::FormatItem{
    public:
    PercentSignFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << "%";
    }
    };

    // %T
    class TabFormatItem: public LogFormatter::FormatItem{
    public:
    TabFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << '\t';
    }
    };

    // %n
    class NewLineFormatItem: public LogFormatter::FormatItem{
    public:
    NewLineFormatItem(const std::string& str = ""){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << std::endl;
    }
    };

    // 格式化模板参数外的字符串
    class StringFormatItem: public LogFormatter::FormatItem{
    public:
    StringFormatItem(const std::string& str = ""): m_string(str){}
    void format(std::ostream& os, LogEvent::ptr event) override {
    os << m_string;
    }

    private:
    std::string m_string;
    };

format

format使用多态,逐个调用FormatItem派生类的format方法,将日志信息写入os

1
2
3
4
5
6
std::ostream& LogFormatter::format(std::ostream& os, LogEvent::ptr event){
for(auto& formatItem : m_items){
formatItem->format(os, event);
}
return os;
}

init

init()是LogFormatter的实现重点,其作用是将m_pattern解析为一个FormatItem::ptr数组,并存入m_item

init的逻辑大致只有以下两步,但实现方式需要多琢磨

  1. 使用一个简单的基于状态机的解析方案,解析模板字符串与常规字符

    • 支持解析上面列举的转义字符,并且支持将连续的普通字符合并成一个字符串
    text
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    | 当前字符是否是%?
    -->是
    -->状态机状态为正在解析模板参数
    - 当前字符为转义%,将模板参数记录到数组
    -->状态机状态为正在解析常规字符串
    - 当前常规字符串解析完成,保存已解析的常规字符串
    - 准备开始解析模板参数,状态机状态变为正在解析模板参数
    -->否
    -->状态机状态为正在解析模板参数
    - 将模板参数记录到数组(对于日期格式(模板参数为d),还需要进一步进行处理)
    - 解析完模板参数,状态机状态变为正在解析常规字符串
    -->状态机状态为正在解析常规字符串
    - 记录字符到字符串
  2. 创建模板参数对应的FormatItem对象(使用宏函数)

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
void LogFormatter::init(){
// <type, str>
// type: 为0时表示为普通字符串,为1时表示为需要解析的模板参数
// str: 存储字符串内容(普通字符串 or 模板参数)
std::vector<std::pair<int, std::string>> vec;

// 模板参数中只有%d(日期时间)需要额外存储模板字符串,所以使用一个单独的变量dataFormat来存储
std::string dateFormat;
// 存储常规字符串
std::string normalString;
// 状态机,true表示正在解析普通字符串,false表示正在解析模板参数或模板字符串
bool parsing_string = true;

for(size_t i = 0; i < m_pattern.size(); ++i){
std::string c = std::string(1, m_pattern[i]);
if(c == "%"){
if(parsing_string){
// 解析常规字符串时碰到%,本段常规字符串的解析完成,状态机变为解析模板字符串模式
if(!normalString.empty()){
vec.push_back(std::make_pair(0, normalString));
normalString.clear();
}
parsing_string = false;
continue;
} else {
// 解析模板参数时碰到%,说明是转义%
vec.push_back(std::make_pair(1, c));
parsing_string = true;
}
} else {
if(parsing_string){
// 解析常规字符串时,不断将当前字符加入normalString,直到遇到%
normalString += c;
continue;
} else {
// 解析模板字符:直接将模板字符加入vec
vec.push_back(std::make_pair(1, c));

// 对日期时间(%d),还需要另外解析dateFormat
if(c == "d"){
++i;
if(m_pattern[i] != '{'){
// %d后没有跟 {},dateFormat为空,解析结果依赖DataFormatItem类的默认实现
continue;
} else {
++i;
// 将 {} 之间的所有字符都加入dateFormat
while(i < m_pattern.size() && m_pattern[i] != '}'){
dateFormat.push_back(m_pattern[i]);
++i;
}
// 如果遍历到m_pattern尾部都没有遇到'}',说明大括号没有闭合,解析错误
if(i == m_pattern.size() && m_pattern[i - 1] != '}'){
// log中加入错误信息
vec.push_back(std::make_pair(0, "<<Pattern Error>>"));
dateFormat.clear();
m_error = true;
}
}
}
// 模板字符串解析完成,状态机转为默认状态(即解析常规字符串)
parsing_string = true;
}
}
}
// 将尾部的常规字符串也加入vec
if(!normalString.empty()){
vec.push_back(std::pair(0, normalString));
normalString.clear();
}

// 字符串到具体FormatItem类的映射,相当于一个简易工厂类
// 利用宏函数初始化map的成员,两个宏参数:
// str:通过#str将str的内容字符串化,指定std::function需要生成FormatItem的哪个派生类
// std::function:接收一个字符串,用该字符串初始化指定的派生类
static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)>> s_format_items = {
#define XX(str, C) {#str, [](const std::string& fmt){ return FormatItem::ptr(new C(fmt)); } }

XX(m, MessageFormatItem), // m: 消息
XX(p, LevelFormatItem), // p: 日志级别
XX(c, LoggerNameFormatItem), // c: 日志器名称
XX(d, DateTimeFormatItem), // d: 日期时间
XX(r, ElapseFormatItem), // r: 累计毫秒数
XX(f, FilenameFormatItem), // f: 文件名
XX(l, LineFormatItem), // l: 行号
XX(t, ThreadIdFormatItem), // t: 线程号
XX(F, FiberIdFormatItem), // F: 协程号
XX(N, ThreadNameFormatItem), // N: 线程名称
XX(%, PercentSignFormatItem), // %: 百分号
XX(T, TabFormatItem), // T: 制表符
XX(n, NewLineFormatItem), // n: 换行符

#undef XX
};

for(auto& item : vec){
// 常规字符串
if(item.first == 0){
m_items.push_back(FormatItem::ptr(new StringFormatItem(item.second)));
}
// 模板参数
else {
// 日期时间进行特殊处理,使用dateFormat初始化,其他模板参数使用item.second初始化
if(item.second == "d"){
m_items.push_back(FormatItem::ptr(new DateTimeFormatItem(dateFormat)));
} else {
auto it = s_format_items.find(item.second);
if(it != s_format_items.end()){
m_items.push_back(FormatItem::ptr(it->second(item.second)));
} else {
// 不合法的模板参数,log记录错误信息
m_items.push_back(FormatItem::ptr(new StringFormatItem("<<error_format %" + item.second + ">>")));
}
}
}
}
}

LogAppender

LogAppender用于将一个日志事件输出到对应的输出地

LogAppender是一个虚类,可以派生出不同的具体实现。不同类型的Appender通过重载log方法来实现往不同的目的地进行输出

基类指定了格式化器(LogFormatter),用户可以自定义格式化器,如果用户没有定义,则使用默认格式化器

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
// .h

// 日志输出地
class LogAppender{
public:
using ptr = std::shared_ptr<LogAppender>;
using MutexType = SpinLock;

LogAppender(LogFormatter::ptr defaultFormatter)
: m_defaultFormatter(defaultFormatter){};

virtual ~LogAppender(){};

virtual void log(LogEvent::ptr event) = 0;

void setFormatter(LogFormatter::ptr formatter);

LogFormatter::ptr getFormatter() const ;

virtual std::string toYamlString() const = 0;

protected:
// Logger在调用Appender之前已经判断了日志级别,这里不需要再重复判断level
// LogLevel::Level m_level;
LogFormatter::ptr m_formatter; // 自定义formatter
LogFormatter::ptr m_defaultFormatter; // 默认formatter
mutable MutexType m_mutex;
};

基类的get/set方法

1
2
3
4
5
6
7
8
9
10
11
void LogAppender::setFormatter(LogFormatter::ptr formatter) {
// 创建一个MutexType的范围锁
MutexType::Lock lock(m_mutex);
m_formatter = formatter;
}

LogFormatter::ptr LogAppender::getFormatter() const {
// 创建一个MutexType的范围锁
MutexType::Lock lock(m_mutex);
return m_formatter;
}

StdoutLogAppender——输出到终端

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
// .h
class StdoutLogAppender: public LogAppender{
public:
StdoutLogAppender();

using ptr = std::shared_ptr<StdoutLogAppender>;

void log(LogEvent::ptr event) override;

std::string toYamlString() const override;
};

// .cpp
StdoutLogAppender::StdoutLogAppender()
: LogAppender(LogFormatter::ptr(new LogFormatter)){
};

void StdoutLogAppender::log(LogEvent::ptr event){
if(m_formatter){
MutexType::Lock lock(m_mutex);
m_formatter->format(std::cout, event);
} else {
MutexType::Lock lock(m_mutex);
m_defaultFormatter->format(std::cout, event);
}
}

FileLogAppender——输出到文件

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
47
48
// .h
class FileLogAppender: public LogAppender{
public:
using ptr = std::shared_ptr<FileLogAppender>;

FileLogAppender(const std::string& filename);

void log(LogEvent::ptr event) override;

std::string toYamlString() const override;

private:
// 重新打开文件(如果文件是打开状态,则先关闭它再打开)
// 打开成功返回true,否则返回false
bool reopen();

private:
std::string m_filename; // 输出目的地的文件名
std::ofstream m_filestream; // 输出目的地的文件流
uint64_t m_lastTime; // 上次打开时间
};

// .cpp
FileLogAppender::FileLogAppender(const std::string& filename)
: LogAppender(LogFormatter::ptr(new LogFormatter))
, m_filename(filename){
}

void FileLogAppender::log(LogEvent::ptr event){
// 定时reopen文件,防止因文件被其他线程删除等原因而log失败
uint64_t now = event->getTime();
if(now >= (m_lastTime + 3)){
if(!reopen()){
std::cerr << "reopen fail, file name=" << m_filename << std::endl;
}
m_lastTime = now;
}
// 上范围锁
MutexType::Lock lock(m_mutex);
// log
if(m_filestream){
if(m_formatter){
m_formatter->format(m_filestream, event);
} else {
m_defaultFormatter->format(m_filestream, event);
}
}
}

Logger

Logger负责进行日志输出

  • Logger的多线程安全:因为log操作的阻塞时间较短,所以使用自旋锁来保证多线程安全
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
// 日志器
class Logger{
public:
friend LogManager;

using ptr = std::shared_ptr<Logger>;
using MutexType = SpinLock;

Logger(const std::string& name = "root");

void log(LogEvent::ptr event);

void addAppender(LogAppender::ptr appender);
void delAppender(LogAppender::ptr appender);
void clearAppenders();

LogLevel::Level getLevel() const { return m_level; };
void setLevel(LogLevel::Level level);

std::string getName() const { return m_name; }

std::string toYamlString() const;

private:
// 日志器的名称
std::string m_name;
// 日志级别,只有高于该级别的日志事件才会被输出
LogLevel::Level m_level;
// 日志输出地的集合
std::list<LogAppender::ptr> m_appenders;
// 主日志器,当logger没有定义appender时,其行为与该主日志器一致
Logger::ptr m_root;
// 锁
mutable MutexType m_mutex;
};

log

log接受一个日志事件,先判断该日志事件的级别是否大于本日志器的日志级别。如果日志级别满足条件,则调用appender的log方法将日志事件进行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Logger::log(LogEvent::ptr event){
// 仅输出大于m_level的日志事件
if(event->getLevel() >= m_level){
// 上范围锁
MutexType::Lock lock(m_mutex);
// 对所有appender进行log
if(!m_appenders.empty()){
for(auto& appender: m_appenders){
appender->log(event);
}
} else if(m_root) {
// 如果没有配置appender,就使用主日志器m_root的appenders进行log
for(auto& appender : m_root->m_appenders){
appender->log(event);
}
}
}
}

appender操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Logger::addAppender(LogAppender::ptr appender){
MutexType::Lock lock(m_mutex);
m_appenders.push_back(appender);
}

void Logger::delAppender(LogAppender::ptr appender){
MutexType::Lock lock(m_mutex);
for(auto it = m_appenders.begin(); it != m_appenders.end(); ++it){
if(*it == appender){
m_appenders.erase(it);
break;
}
}
}

void Logger::clearAppenders(){
MutexType::Lock lock(m_mutex);
m_appenders.clear();
}

toYamlString

将Logger信息输出为Yaml格式的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::string Logger::toYamlString() const {
YAML::Node node(YAML::NodeType::Map);
node["name"] = m_name;
node["level"] = LogLevel::ToString(m_level);
// 读取m_appenders,上范围锁
MutexType::Lock lock(m_mutex);

for(const auto& a : m_appenders){
node["appenders"].push_back(YAML::Load(a->toYamlString()));
}
std::stringstream ss;
ss << node;
return ss.str();
}

LogEventWrap

LogEventWrap是一个RAII类,它封装了一个Logger和LogEvent,在其析构时调用Logger的log方法提交日志事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// .h

class LogEventWrap{
public:
LogEventWrap(Logger::ptr logger, LogEvent::ptr event);

~LogEventWrap();

LogEvent::ptr getEvent() const { return m_event; }

std::stringstream& getSS();

private:
Logger::ptr m_logger;
LogEvent::ptr m_event;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .cpp

LogEventWrap::LogEventWrap(Logger::ptr logger, LogEvent::ptr event)
: m_logger(logger)
, m_event(event){
}

// LogEventWrap析构时对日志事件进行log
LogEventWrap::~LogEventWrap(){
m_logger->log(m_event);
}

std::stringstream& LogEventWrap::getSS(){
return m_event->getSS();
}

LogManager

LogManager用于统一管理所有的日志器,提供日志器的创建与获取方法

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
// Logger的管理器,管理主日志器以及所有Logger
// LogManager是创建Logger的唯一方式
class LogManager{
public:
friend Singleton<LogManager>;

using MutexType = SpinLock;

Logger::ptr getLogger(std::string loggerName);

// todo: init实现从配置文件中加载日志配置
void init();

Logger::ptr getRoot() const { return m_root; }

std::string toYamlString() const;

private:
// 单例模式,私有的构造函数
LogManager();

private:
// 主日志器
Logger::ptr m_root;
// 管理包括m_root在内所有日志器的容器
std::map<std::string, Logger::ptr> m_loggers;
// Mutex
mutable MutexType m_mutex;
};

// 使用单例模式管理LogManager
using LoggerMgr = Singleton<LogManager>;

Singleton单例类

基于单例模式的单例模板类,通过静态函数创建指定类的静态对象

1
2
3
4
5
6
7
8
9
// Singleton.h
template<typename T, typename X = void, int N = 0>
class Singleton{
public:
static T* GetInstance(){
static T v;
return &v;
}
};

通过类型别名为用户提供创建LogManager单例的方法:

1
2
// log.h
using LoggerMgr = Singleton<LogManager>;

构造函数

构造时默认创建一个root日志器,将日志信息输出到终端

1
2
3
4
5
6
LogManager::LogManager(){
m_root.reset(new Logger("root"));
m_root->addAppender(std::make_shared<StdoutLogAppender>());
m_loggers[m_root->getName()] = m_root;
init();
}

getLogger

getLogger根据日志器名从管理的日志器中返回一个Logger,否则以该名字创建一个Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Logger::ptr LogManager::getLogger(std::string loggerName){
// 需要读取m_loggers,上范围锁
MutexType::Lock lock(m_mutex);

auto it = m_loggers.find(loggerName);
// 找到logger就返回该logger
if(it != m_loggers.end()){
return it->second;
}
// 否则以该名字创建一个logger,其主日志器指向LogManager的主日志器
Logger::ptr newLogger(std::make_shared<Logger>(loggerName));
newLogger->m_root = m_root;
m_loggers[loggerName] = newLogger;
return newLogger;
}

toYamlString

LoggerManager的信息输出为Yaml格式的字符串

1
2
3
4
5
6
7
8
9
10
std::string LogManager::toYamlString() const {
MutexType::Lock lock(m_mutex);
YAML::Node node(YAML::NodeType::Map);
for(const auto& l : m_loggers){
node["logs"].push_back(YAML::Load(l.second->toYamlString()));
}
std::stringstream ss;
ss << node;
return ss.str();
}

实用辅助函数

使用流式方式写入日志

使用流式方式将日志级别level的日志写入logger

基于宏函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// .h
// 使用流式方式将日志级别level的日志写入logger
// 这里宏函数替换的是代码段,所以不能使用inline替代宏函数
#define SYLAR_LOG_LEVEL(logger, level) \
if(logger->getLevel() <= level) \
sylar::LogEventWrap(logger, std::make_shared<sylar::LogEvent>(logger->getName(), level, __FILE__, __LINE__\
, 0, sylar::GetThreadId(), sylar::GetFiberId(), time(0), sylar::GetThreadName())).getSS()

#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)

#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)

#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)

#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)

#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)

使用格式化方式写入日志

基于inline实现

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
// 使用格式化方式将日志级别level的日志写入logger
// 使用可变参数模板+inline替代宏函数
template<typename... Arg>
inline void SYLAR_LOG_FMT_LEVEL(sylar::Logger::ptr logger, sylar::LogLevel::Level level, const char* fmt, Arg&&... args){
if(logger->getLevel() <= level){
sylar::LogEventWrap(logger, std::make_shared<sylar::LogEvent>(logger->getName(), level, __FILE__, __LINE__
, 0, sylar::GetThreadId(), 42, time(0), "Thread")).getEvent()->format(fmt, args...);
}
}

template<typename... Arg>
inline void SYLAR_LOG_FMT_DEBUG(sylar::Logger::ptr logger, const char* fmt, Arg&&... args){
SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, fmt, args...);
}

template<typename... Arg>
inline void SYLAR_LOG_FMT_INFO(sylar::Logger::ptr logger, const char* fmt, Arg&&... args){
SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, fmt, args...);
}

template<typename... Arg>
inline void SYLAR_LOG_FMT_WARN(sylar::Logger::ptr logger, const char* fmt, Arg&&... args){
SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, fmt, args...);
}

template<typename... Arg>
inline void SYLAR_LOG_FMT_ERROR(sylar::Logger::ptr logger, const char* fmt, Arg&&... args){
SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, fmt, args...);
}

template<typename... Arg>
inline void SYLAR_LOG_FMT_FATAL(sylar::Logger::ptr logger, const char* fmt, Arg&&... args){
SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, fmt, args...);
}

获取日志器

1
2
3
4
// 获取LogManager中的主日志器(root)
#define SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()->getRoot()
// 根据名字获取logger
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(name)

模块使用

例子1

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
int main(int argc, char** argv) {
// 自定义输出地与输出格式
sylar::FileLogAppender::ptr file_appender(new sylar::FileLogAppender("./log.txt"));
sylar::LogFormatter::ptr fmt(new sylar::LogFormatter("%d%T%p%T%m%n"));
file_appender->setFormatter(fmt);

// 获取LogerManager单例
auto mgr = sylar::LoggerMgr::GetInstance();

// 根日志器新增appender
mgr->getRoot()->addAppender(file_appender);

// 根据LogManager单例创建Logger
auto logger = mgr->getLogger("Logger01");
logger->addAppender(std::make_shared<sylar::StdoutLogAppender>());

// 使用宏函数流式输出日志信息
std::string str("LoggerManager");
SYLAR_LOG_ERROR(mgr->getRoot()) << "test " << str << " Error";
SYLAR_LOG_INFO(logger) << "test logger01";

// 实用格式化方式输出日志信息
SYLAR_LOG_FMT_DEBUG(mgr->getRoot(), "test %s Debug <%s>", str.c_str(), "using inline and template");

return 0;
}

例子2

1
2
3
4
5
6
7
8
9
// 获取system日志器
static sylar::Logger::ptr g_logger1 = SYLAR_LOG_NAME("system");
// 使用system日志器流式打印INFO级别的日志
SYLAR_LOG_INFO(g_logger1) << "test logger01";

// 获取root日志器
static sylar::Logger::ptr g_logger2 = SYLAR_LOG_ROOT();
// 使用root日志器流式打印INFO级别的日志
SYLAR_LOG_INFO(g_logger2) << "test logger02";