一个简洁的 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 255Uclass 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 := appCUR_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 风格的格式化输出
这个日志模块虽然简洁,但提供了基本日志功能所需的核心特性,适合在中小型项目中使用。