一个简洁的 C++ 日志模块实现
1. 引言
日志功能在软件开发中扮演着至关重要的角色,它帮助开发者追踪程序执行过程、诊断问题以及监控系统运行状态。本文介绍一个使用 C++ 实现的轻量级日志模块,该模块支持多日志级别、线程安全,并提供了简洁易用的接口。
2. 代码实现
2.1 主程序 (main.cpp)
#include "log.h"
int main(int argc, char *argv[])
{
Log::GetInstance().Init("./log");
Log::GetInstance().WriteLog(ERROR, "error");
Log::GetInstance().WriteLog(WARNING, "warning");
Log::GetInstance().WriteLog(DEBUG, "debug");
Log::GetInstance().WriteLog(INFO, "info");
return 0;
}
2.2 头文件 (log.h)
#ifndef LOG_H
#define LOG_H
#include <iostream>
#include <pthread.h>
#include <cstdio>
using namespace std;
// 日志级别枚举
typedef enum {
ERROR = 0,
WARNING,
DEBUG,
INFO,
LEVEL_MAX
} LogLevel_en;
// 日志状态枚举
typedef enum {
LOG_INIT_STATE,
LOG_READY_STATE,
LOG_INVALID_STATE
} LogState_en;
#define LOG_NUM 255U
class Log {
private:
char m_log_buf[LOG_NUM]; // 日志缓冲区
pthread_mutex_t m_mutex; // 互斥锁,保证线程安全
FILE *m_log_fd; // 日志文件指针
LogState_en m_log_state; // 日志模块状态
public:
static Log& GetInstance(void); // 获取单例实例
void Init(const char *dir_file_name); // 初始化日志系统
void WriteLog(LogLevel_en log_level, const char* format, ...); // 写日志接口
private:
Log(void) {
m_log_state = LOG_INVALID_STATE;
};
~Log(void) = default;
};
#endif
2.3 实现文件 (log.cpp)
#include "log.h"
#include <ctime>
#include <cstring>
#include <cstdarg>
// 获取日志单例实例
Log& Log::GetInstance(void)
{
static Log instance;
return instance;
}
// 初始化日志系统
void Log::Init(const char *dir_file_name)
{
if(nullptr != dir_file_name)
{
m_log_state = LOG_INIT_STATE;
cout << "Log file: " << dir_file_name << endl;
m_log_fd = fopen(dir_file_name, "a");
if (m_log_fd != nullptr) {
m_log_state = LOG_READY_STATE;
}
}
}
// 写入日志信息
void Log::WriteLog(LogLevel_en log_level, const char* format, ...)
{
// 参数有效性检查
if((LEVEL_MAX <= log_level) || (nullptr == format)) {
return;
}
// 日志状态检查
if (LOG_READY_STATE != m_log_state) {
return;
}
// 设置日志级别字符串
char buffer_log_level[16] = {0};
switch (log_level) {
case ERROR:
strcpy(buffer_log_level, "[error]:");
break;
case WARNING:
strcpy(buffer_log_level, "[warning]:");
break;
case DEBUG:
strcpy(buffer_log_level, "[debug]:");
break;
case INFO:
strcpy(buffer_log_level, "[info]:");
break;
default:
cout << "Invalid log level" << endl;
return;
}
// 设置时间戳字符串
time_t time_val = time(nullptr);
struct tm *p_tm_val = localtime(&time_val);
char buffer_time[48] = {0};
snprintf(buffer_time, 48, "%.4d-%.2d-%.2d-%.2d-%.2d-%.2d:",
p_tm_val->tm_year + 1900,
p_tm_val->tm_mon + 1,
p_tm_val->tm_mday,
p_tm_val->tm_hour,
p_tm_val->tm_min,
p_tm_val->tm_sec
);
// 加锁保证线程安全
pthread_mutex_lock(&m_mutex);
// 组装日志前缀
memset(m_log_buf, 0, sizeof(m_log_buf));
u_int8_t log_len = strlen(buffer_log_level) + strlen(buffer_time) + 1;
int n = snprintf(m_log_buf, log_len, "%s%s", buffer_log_level, buffer_time);
if(n > 0) {
// 处理可变参数
va_list variable_list;
va_start(variable_list, format);
int m = vsnprintf(m_log_buf + n, LOG_NUM - n - 1, format, variable_list);
va_end(variable_list);
if(m > 0) {
// 输出到控制台和文件
cout << m_log_buf << endl;
m_log_buf[n + m] = '\n';
m_log_buf[n + m + 1] = '\0';
fputs(m_log_buf, m_log_fd);
fflush(m_log_fd); // 确保数据写入磁盘
} else {
cout << "vsnprintf error!" << endl;
}
} else {
cout << "snprintf error!" << endl;
}
// 释放锁
pthread_mutex_unlock(&m_mutex);
}
2.4 Makefile 构建配置
# 目标程序名称
TGT := app
CUR_DIR := $(shell pwd)
# 源文件设置
SRC := $(wildcard *.cpp)
OBJ := $(patsubst %.cpp,%.o,$(SRC))
# 编译选项设置
CPPFLAGS := -I.
CPPFLAGS += -pthread
CPPFLAGS += -I${CUR_DIR}/include
# 编译器标志
CXXFLAGS := -Wall -O2
# 默认构建目标
all: $(TGT)
@echo "Build successful"
$(TGT): $(OBJ)
$(CXX) -std=c++11 $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
%.o: %.cpp
$(CXX) -std=c++11 $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
# 清理构建文件
clean:
ifneq ($(wildcard $(OBJ)),)
@rm $(OBJ)
else
@echo "No object files to remove"
endif
ifneq ($(wildcard $(TGT)),)
@rm $(TGT)
else
@echo "No target executable to remove"
endif
# 仅清理对象文件
obj_clean:
ifneq ($(wildcard $(OBJ)),)
@rm $(OBJ)
else
@echo "No object files to remove"
endif
.PHONY: obj_clean clean all
3. 编译与运行结果
执行构建命令:
$ make
g++ -std=c++11 -I. -pthread -I/mnt/code/01_comprehensive/log_demo/include -Wall -O2 -c log.cpp -o log.o
g++ -std=c++11 -I. -pthread -I/mnt/code/01_comprehensive/log_demo/include -Wall -O2 -c main.cpp -o main.o
g++ -std=c++11 -I. -pthread -I/mnt/code/01_comprehensive/log_demo/include -Wall -O2 log.o main.o -o app
Build successful
运行程序:
$ ./app
Log file: ./log
[error]:2024-06-29-11-33-03:error
[warning]:2024-06-29-11-33-03:warning
[debug]:2024-06-29-11-33-03:debug
[info]:2024-06-29-11-33-03:info
查看生成的日志文件:
$ cat log
[error]:2024-06-29-11-23-43:error
[warning]:2024-06-29-11-23-43:warning
[debug]:2024-06-29-11-23-43:debug
[info]:2024-06-29-11-23-43:info
4. 设计特点
- 单例模式:确保整个应用程序中只有一个日志实例
- 线程安全:使用互斥锁保护共享资源,支持多线程环境
- 多日志级别:支持 ERROR、WARNING、DEBUG、INFO 四种级别
- 时间戳:每条日志都包含精确到秒的时间信息
- 双重输出:日志同时输出到控制台和文件
- 格式化支持:支持 printf 风格的格式化输出
这个日志模块虽然简洁,但提供了基本日志功能所需的核心特性,适合在中小型项目中使用。