C++实现日志库

调用日志库有两种高效的方式,基本思路是全局定义宏:这里使用第二种

  • 1.使用operator<<重载,将log信息当作流,类似于cout<<
  • 2.使用函数调用,将log信息当作函数参数,类似于print()
1
2
3
4
5
6
LOG_INFO<<"服务器启动成功,端口:"<<port;
LOG_WARN<<"连接超时:"<<connId;
LOG_ERROR<<"数据库连接失败"
LOG_INFO("服务器启动成功,端口:%d",port);
LOG_WARN("连接超时: %d",connId);
LOG_ERROR("数据库连接失败");

日志库功能

日志库(Logging Library)是用于记录运行时信息的工具组件。

作为server唯一输出的内容,日志库显得尤为重要

基本功能

  • 日志级别 : 支持不同级别(如 DEBUG、INFO、WARN、ERROR、FATAL),方便控制输出粒度。
  • 时间戳:每条日志通常包含精确的时间戳,帮助排查问题。
  • 线程信息: 可选地记录线程 ID。

日志输出

日志库可以将日志信息输出到不同的目标,包括:

  • 终端输出:打印到终端或命令行界面,用于开发和调试。
  • 文件输出:将日志写入文件中,持久存储在日志文件中。
  • 网络输出:通过网络将日志发送到远程服务器或日志收集系统。
  • 数据库:有些日志库允许将日志信息存储到数据库中。

输出示例

1
2
3
4
[2025-03-14 03:40:27][TRACE] updateChannel Poller.cc:49: fd=3 events=3
[2025-03-14 03:40:27][TRACE] EventLoop EventLoop.cc:16: EventLoop created 0x7ffd65bb0120 in thread 12521
[2025-03-14 03:40:28][TRACE] poll Poller.cc:15: 1 events happended
[2025-03-14 03:40:28][TRACE] readTimerfd TimerQueue.cc:60: TimerQueue::handleRead() reads 8 at 1745034028.327096

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
class Logger
{
public:
enum LogLevel
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
NUM_LOG_LEVALS,
};
static void log(SourceFile file, int line, LogLevel level, const char *fmt, ...);
static void log(SourceFilefile, int line, LogLevel level, const char *func, const char *fmt...);
static void logSys(SourceFile file, int line, bool fatal, const char *fmt, ...);

static LogLevel logLevel();//设置日当前志级别
static void setLogLevel(LogLevel level);
typedef void (*OutputFunc)(const char *msg, int len);
typedef void (*FlushFunc)();
static void setOutput(OutputFunc);//设置输出方式,自定义回调如输出到终端或者输出到文件
static void setFlush(FlushFunc);//设置刷新缓冲方式

private:
Logger() = delete;//不允许实例化Logger类
};

配置的SouceFIle是一个小工具类,用于从__FILE__中提取文件名,避免日志中出现过长的路径

1
2
3
4
5
6
7
8
9
10
11
class SourceFile
{
public:
const char *data_;
int size_;

template <int N>
SourceFile(const char (&arr)[N]) : data_(arr), size_(N - 1);
explicit SourceFile(const char *filename) : data_(filename);
};

日志输出逻辑

我们重点实现的格式化日志调用方式是这样的:

1
Logger::log(__FILE__,__LINE__,Logger::INFO,fmt,...);

实现代码核心逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void formatTime(char *buf, size_t size)
{
time_t now = time(nullptr);
if (now != t_lastSecond)
{
t_lastSecond = now;
struct tm tm_time;
gmtime_r(&now, &tm_time);
snprintf(buf, size, "%4d%02d%02d %02d:%02d:%02d",
tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
}
}

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
void Logger::log(SourceFile file, int line, LogLevel level, const char *fmt, ...)
{
if (level < g_logLevel)
return;

std::string time_buf;
Timestamp timestamp = Timestamp::now();
time_buf = timestamp.toFormattedString(showMicroseconds);
/*初始化时间也可:
char timebuf[64];
formatTime(buf,sizeof(buf));
这里使用时间戳类避免再次实现*/

//格式化用户传入的消息
char msg_buf[1024];
va_list args;
va_start(args, fmt);
vsnprintf(msg_buf, sizeof(msg_buf), fmt, args);
va_end(args);

//拼接最终输出内容
char line_buf[2048];
snprintf(line_buf, sizeof(line_buf), "[%s][%s] %s:%d: %s\n",
time_buf.c_str(), levelNames[level], file.data_, line, msg_buf);

//加锁输出日志
std::lock_guard<std::mutex> lock(g_mutex);
g_output(line_buf, strlen(line_buf));
g_flush();
//如果是FATAL级别,终止程序
if (level == FATAL)
abort();
}

日志宏封装

使用宏是为了让调用更简洁,自动传入__FILE__和__LINE__,日志级别等元信息:

1
2
#define LOG_INFO(fmt, ...) \
Logger::log(Logger::SourceFile(__FILE__), __LINE__, Logger::INFO, fmt, ##__VA_ARGS__)

其他宏如LOG_DEBUG,LOG_ERROR等原理类似,且按需包含函数名

日志等级设置

可以动态设置日志等级:

1
Logger::setLogLevel(Logger::WARN);//只显示WARN以上的日志

日志等级是静态变量:

1
static Logger::LogLevel g_logLevel=Logger::INFO;

自定义输出与刷新

可以通过函数指针实现对输出的灵活回调:

1
2
3
Logger::setOutput([](const char*msg,int len)
{fwrite(msg,1,len,stdout);});
//输出到终端

也可以重定向输出到文件:

1
2
3
FILE*fp=fopen("log.txt","a");
Logger::setOutput([fp](const char* msg,int len)
{fwrite(msg,1,len,fp);});

添加日志颜色

更改levelNames[]

1
2
3
4
5
6
7
8
9
static const char *levelNames[] = {
"TRACE",
"DEBUG",
"INFO",
"WARN",
"ERROR",
"FATAL",
};

1
2
3
4
5
6
7
8
static const char *levelNames[] = {
"\033[36mTRACE\033[0m",
"\033[34mDEBUG\033[0m" ,
"\033[32mINFO\033[0m" ,
"\033[33mWARN\033[0m",
"\033[31mERROR\033[0m",
"\033[35mFATAL\033[0m",
};

注意事项

如果输出到文件,也会写入颜色控制代码(就是那些\033[32m),也会污染日志文件,可以通过条件判断是否加颜色 (比如enableColorLog)决定是否加颜色

源代码

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
/*Logger.h*/
#ifndef LOGGER_H
#define LOGGER_H
#include <cstring>
namespace mylib
{
#define LOG_TRACE(fmt, ...) \
if (mylib::Logger::logLevel() <= mylib::Logger::TRACE) \
mylib::Logger::log(mylib::Logger::SourceFile(__FILE__), __LINE__, mylib::Logger::TRACE, __func__, fmt, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) \
if (mylib::Logger::logLevel() <= mylib::Logger::DEBUG) \
mylib::Logger::log(mylib::Logger::SourceFile(__FILE__), __LINE__, mylib::Logger::DEBUG, __func__, fmt, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) \
mylib::Logger::log(mylib::Logger::SourceFile(__FILE__), __LINE__, mylib::Logger::INFO, fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) \
mylib::Logger::log(mylib::Logger::SourceFile(__FILE__), __LINE__, mylib::Logger::WARN, fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) \
mylib::Logger::log(mylib::Logger::SourceFile(__FILE__), __LINE__, mylib::Logger::ERROR, fmt, ##__VA_ARGS__)
#define LOG_FATAL(fmt, ...) \
mylib::Logger::log(mylib::Logger::SourceFile(__FILE__), __LINE__, mylib::Logger::FATAL, fmt, ##__VA_ARGS__)
#define LOG_SYSERR(fmt, ...) \
mylib::Logger::logSys(mylib::Logger::SourceFile(__FILE__), __LINE__, false, fmt, ##__VA_ARGS__)
#define LOG_SYSFATAL(fmt, ...) \
mylib::Logger::logSys(mylib::Logger::SourceFile(__FILE__), __LINE__, true, fmt, ##__VA_ARGS__)

class Logger
{
public:
enum LogLevel
{ TRACE,DEBUG,INFO,WARN,ERROR,FATAL,NUM_LOG_LEVALS,};
class SourceFile
{
public:
const char *data_;
int size_;

template <int N>
SourceFile(const char (&arr)[N]) : data_(arr), size_(N - 1)
{
const char *slash = strrchr(data_, '/');
if (slash)
{
data_ = slash + 1;
size_ -= static_cast<int>(data_ - arr);
}
}
explicit SourceFile(const char *filename) : data_(filename)
{
const char *slash = strrchr(filename, '/');
if (slash)
{ data_ = slash + 1;}
size_ = static_cast<int>(strlen(data_));
}
};

static void log(SourceFile file, int line, LogLevel level, const char *fmt, ...);
static void log(SourceFile file, int line, LogLevel level, const char *func, const char *fmt, ...);
static void logSys(SourceFile file, int line, bool fatal, const char *fmt, ...);

static LogLevel logLevel();
static void setLogLevel(LogLevel level);
typedef void (*OutputFunc)(const char *msg, int len);
typedef void (*FlushFunc)();
static void setOutput(OutputFunc);
static void setFlush(FlushFunc);

private:
Logger() = delete;
};

extern Logger::LogLevel g_logLevel;
extern bool showMicroseconds;
extern bool enableColorLog;

};

#endif
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
144
145
146
147
148
149
150
/*Logger.cc*/
#include "Logger.h"
#include "Timestamp.h"//可以使用formatTime函数代替时间戳
#include <mutex>
#include <thread>
#include <cstdarg>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <string>
namespace mylib
{

Logger::LogLevel g_logLevel = Logger::TRACE;
bool showMicroseconds = false;
bool enableColorLog = true;

static std::mutex g_mutex;
static Logger::OutputFunc g_output = [](const char *msg, int len)
{ fwrite(msg, 1, len, stdout); };
static Logger::FlushFunc g_flush = []()
{ fflush(stdout); };

__thread char t_errnobuf[512];
__thread char t_time[32];
__thread time_t t_lastSecond;

const char *strerror_tl(int savedErrno)
{
return strerror_r(savedErrno, t_errnobuf, sizeof(t_errnobuf));
}

static void formatTime(char *buf, size_t size)
{
time_t now = time(nullptr);
if (now != t_lastSecond)
{
t_lastSecond = now;
struct tm tm_time;
gmtime_r(&now, &tm_time);
snprintf(buf, size, "%4d%02d%02d %02d:%02d:%02d",
tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
}
}

static const char *levelNames[] = {
mylib::enableColorLog ? "\033[36mTRACE\033[0m" : "TRACE",
mylib::enableColorLog ? "\033[34mDEBUG\033[0m" : "DEBUG",
mylib::enableColorLog ? "\033[32mINFO\033[0m" : "INFO",
mylib::enableColorLog ? "\033[33mWARN\033[0m" : "WARN",
mylib::enableColorLog ? "\033[31mERROR\033[0m" : "ERROR",
mylib::enableColorLog ? "\033[35mFATAL\033[0m" : "FATAL",
};
void Logger::log(SourceFile file, int line, LogLevel level, const char *fmt, ...)
{
if (level < g_logLevel)
return;

std::string time_buf;
Timestamp timestamp = Timestamp::now();
time_buf = timestamp.toFormattedString(showMicroseconds);

char msg_buf[1024];
va_list args;
va_start(args, fmt);
vsnprintf(msg_buf, sizeof(msg_buf), fmt, args);
va_end(args);

char line_buf[2048];
snprintf(line_buf, sizeof(line_buf), "[%s][%s] %s:%d: %s\n",
time_buf.c_str(), levelNames[level], file.data_, line, msg_buf);

std::lock_guard<std::mutex> lock(g_mutex);
g_output(line_buf, strlen(line_buf));
g_flush();
if (level == FATAL)
abort();
}

void Logger::log(SourceFile file, int line, LogLevel level, const char *func, const char *fmt, ...)
{
if (level < g_logLevel)
return;

std::string time_buf;
Timestamp timestamp = Timestamp::now();
time_buf = timestamp.toFormattedString(showMicroseconds);

char msg_buf[1024];
va_list args;
va_start(args, fmt);
vsnprintf(msg_buf, sizeof(msg_buf), fmt, args);
va_end(args);

char line_buf[2048];
snprintf(line_buf, sizeof(line_buf), "[%s][%s] %s %s:%d: %s\n",
time_buf.c_str(), levelNames[level], func, file.data_, line, msg_buf);

std::lock_guard<std::mutex> lock(g_mutex);
g_output(line_buf, strlen(line_buf));
g_flush();
if (level == FATAL)
abort();
}

void Logger::logSys(SourceFile file, int line, bool fatal, const char *fmt, ...)
{
int savedErrno = errno;
LogLevel level = fatal ? FATAL : ERROR;
if (level < g_logLevel)
return;

std::string time_buf;
Timestamp timestamp = Timestamp::now();
time_buf = timestamp.toFormattedString(showMicroseconds);

char msg_buf[1024];
va_list args;
va_start(args, fmt);
vsnprintf(msg_buf, sizeof(msg_buf), fmt, args);
va_end(args);

char line_buf[2048];
snprintf(line_buf, sizeof(line_buf), "[%s][%s] %s:%d: %s (errno=%d: %s)\n",
time_buf.c_str(), levelNames[level], file.data_, line, msg_buf, savedErrno, strerror_tl(savedErrno));

std::lock_guard<std::mutex> lock(g_mutex);
g_output(line_buf, strlen(line_buf));
g_flush();
if (fatal)
abort();
}

Logger::LogLevel Logger::logLevel() { return g_logLevel; }

void Logger::setLogLevel(LogLevel level) { g_logLevel = level; }

void Logger::setOutput(OutputFunc output)
{
std::lock_guard<std::mutex> lock(g_mutex);
g_output = output;
}

void Logger::setFlush(FlushFunc flush)
{
std::lock_guard<std::mutex> lock(g_mutex);
g_flush = flush;
}
};