一個簡潔的 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 風格的格式化輸出
這個日志模塊雖然簡潔,但提供了基本日志功能所需的核心特性,適合在中小型項目中使用。