cpp
复制代码
/**
* @file Logger.h
* @author shijb (shijb@guideir.com)
* @brief C++日志类实现
* 支持功能:
* 1. 日志格式:[YYYY-MM-DD
* hh-mm-ss][tid=线程id][文件名][函数名][日志等级]日志信息
* 2. 支持日志路径设置
* 3. 支持日志等级(DEBUG, INFO, WARN, ERROR, FATAL)
* 4. 支持日志回滚(按文件大小)
* 5. 线程安全
* @version 0.1
* @date 2025-12-11
*
* @copyright Copyright (c) 2025
*
*/
#ifndef LOGGER_H
#define LOGGER_H
#include <chrono>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <direct.h>
#include <errno.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <thread>
class Logger {
public:
/**
* @brief 日志等级枚举
*/
enum class Level { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3, FATAL = 4 };
Logger();
~Logger();
/**
* @brief 初始化日志系统
* @param logPath 日志文件路径
* @param level 日志等级
* @param maxFileSize 单个日志文件最大大小(字节),0表示不限制
* @param maxBackupFiles 最大备份文件数
*/
void init(const std::string &logPath, Level level = Level::INFO,
size_t maxFileSize = 10 * 1024 * 1024, int maxBackupFiles = 5);
/**
* @brief 设置日志等级
* @param level 日志等级
*/
void setLevel(Level level);
/**
* @brief 获取当前日志等级
* @return Level 当前日志等级
*/
Level getLevel() const;
/**
* @brief 写日志
* @param level 日志等级
* @param file 文件名
* @param function 函数名
* @param line 行号
* @param message 日志消息
*/
void log(Level level, const std::string &file, const std::string &function,
int line, const std::string &message);
/**
* @brief 关闭日志
*/
void shutdown();
private:
/**
* @brief 检查是否需要回滚日志文件
*/
void checkRollover();
/**
* @brief 执行日志回滚
*/
void doRollover();
/**
* @brief 获取当前时间字符串
* @return std::string 格式化的时间字符串
*/
std::string getCurrentTime() const;
/**
* @brief 获取日志等级字符串
* @param level 日志等级
* @return std::string 等级字符串
*/
std::string levelToString(Level level) const;
/**
* @brief 获取线程ID字符串
* @return std::string 线程ID字符串
*/
std::string getThreadId() const;
private:
std::ofstream logFile_; // 日志文件流
std::string logPath_; // 日志文件路径
Level currentLevel_; // 当前日志等级
size_t maxFileSize_; // 最大文件大小
int maxBackupFiles_; // 最大备份文件数
std::mutex mutex_; // 互斥锁,保证线程安全
bool initialized_; // 是否已初始化
};
#ifdef _MSC_VER
#define FUNCTION_SIGNATURE __FUNCSIG__
#else
#define FUNCTION_SIGNATURE __PRETTY_FUNCTION__
#endif
// 使用标准的函数名宏,提供更好的可移植性
#define LOG_IMPL(level, message) \
do { \
if (m_logger != nullptr) { \
std::ostringstream oss; \
oss << message; \
m_logger->log(Logger::Level::level, __FILE__, FUNCTION_SIGNATURE, \
__LINE__, oss.str()); \
} \
} while (0)
#define LOGD(message) LOG_IMPL(DEBUG, message)
#define LOGI(message) LOG_IMPL(INFO, message)
#define LOGW(message) LOG_IMPL(WARN, message)
#define LOGE(message) LOG_IMPL(ERROR, message)
#define LOGF(message) LOG_IMPL(FATAL, message)
#define DECARLE_LOGGER_FUNC_IN_CLASS \
public: \
void setLogger(Logger *logger) { m_logger = logger; } \
Logger *getLogger() { return m_logger; } \
\
protected: \
Logger *m_logger = nullptr; \
\
public:
#endif //! LOGGER_H
// 日志宏定义,方便使用
cpp
复制代码
/**
* @file Logger.cpp
* @brief Logger类实现
*/
#include "Logger.h"
#include <algorithm>
#include <cstring>
Logger::Logger()
: currentLevel_(Level::INFO), maxFileSize_(10 * 1024 * 1024) // 默认10MB
,
maxBackupFiles_(5), initialized_(false) {}
Logger::~Logger() { shutdown(); }
void Logger::init(const std::string &logPath, Level level, size_t maxFileSize,
int maxBackupFiles) {
std::lock_guard<std::mutex> lock(mutex_);
if (initialized_) {
return;
}
logPath_ = logPath;
currentLevel_ = level;
maxFileSize_ = maxFileSize;
maxBackupFiles_ = maxBackupFiles;
// 创建日志目录
try {
// 提取目录路径
std::string dirPath = logPath;
size_t pos = dirPath.find_last_of("/\\");
if (pos != std::string::npos) {
dirPath = dirPath.substr(0, pos);
// 创建目录(Windows系统)
if (_mkdir(dirPath.c_str()) != 0) {
// 如果目录已存在,errno为EEXIST
if (errno != EEXIST) {
// 检查目录是否真的存在
struct stat info;
if (stat(dirPath.c_str(), &info) != 0) {
throw std::runtime_error("无法创建日志目录: " + dirPath);
}
}
}
}
// 打开日志文件
logFile_.open(logPath, std::ios::out | std::ios::app);
if (!logFile_.is_open()) {
throw std::runtime_error("无法打开日志文件: " + logPath);
}
initialized_ = true;
// 写入初始化日志
logFile_ << "[" << getCurrentTime()
<< "][Logger][init][tid=" << getThreadId()
<< "][INFO]日志系统初始化完成,日志文件: " << logPath << std::endl;
} catch (const std::exception &e) {
std::cerr << "日志初始化失败: " << e.what() << std::endl;
throw;
}
}
void Logger::setLevel(Level level) {
std::lock_guard<std::mutex> lock(mutex_);
currentLevel_ = level;
}
Logger::Level Logger::getLevel() const { return currentLevel_; }
void Logger::log(Level level, const std::string &file,
const std::string &function, int line,
const std::string &message) {
// 检查日志等级
if (static_cast<int>(level) < static_cast<int>(currentLevel_)) {
return;
}
std::lock_guard<std::mutex> lock(mutex_);
if (!initialized_ || !logFile_.is_open()) {
// 如果未初始化,输出到控制台
std::cout << "[" << getCurrentTime() << "] [tid=" << getThreadId() << "] ["
<< file << "] [" << function << "] [lines= " << line << "] ["
<< levelToString(level) << "]" << message << std::endl;
return;
}
// 检查是否需要回滚
checkRollover();
// 提取文件名(去掉路径)
std::string filename = file;
size_t pos = filename.find_last_of("/\\");
if (pos != std::string::npos) {
filename = filename.substr(pos + 1);
}
// 写入日志
logFile_ << "[" << getCurrentTime() << "] [tid: " << getThreadId()
<< "] [file: " << filename << "] [func: " << function
<< "] [lines: " << line << "] [" << levelToString(level) << "]"
<< message << std::endl;
// 立即刷新,确保日志及时写入
logFile_.flush();
}
void Logger::shutdown() {
std::lock_guard<std::mutex> lock(mutex_);
if (initialized_ && logFile_.is_open()) {
logFile_ << "[" << getCurrentTime()
<< "][Logger][shutdown][tid=" << getThreadId()
<< "][INFO]日志系统关闭" << std::endl;
logFile_.close();
}
initialized_ = false;
}
void Logger::checkRollover() {
if (maxFileSize_ == 0 || !logFile_.is_open()) {
return;
}
// 获取当前文件大小
logFile_.flush();
std::streampos pos = logFile_.tellp();
if (pos >= static_cast<std::streampos>(maxFileSize_)) {
doRollover();
}
}
void Logger::doRollover() {
if (!logFile_.is_open()) {
return;
}
logFile_.close();
try {
// 重命名现有备份文件
for (int i = maxBackupFiles_ - 1; i >= 0; --i) {
std::string oldName, newName;
if (i == 0) {
oldName = logPath_;
newName = logPath_ + ".1";
} else {
oldName = logPath_ + "." + std::to_string(i);
newName = logPath_ + "." + std::to_string(i + 1);
}
// 检查文件是否存在
struct stat buffer;
if (stat(oldName.c_str(), &buffer) == 0) {
if (i == maxBackupFiles_ - 1) {
// 删除最旧的备份
std::remove(oldName.c_str());
} else {
// 重命名文件
std::rename(oldName.c_str(), newName.c_str());
}
}
}
// 重新打开日志文件
logFile_.open(logPath_, std::ios::out | std::ios::app);
if (!logFile_.is_open()) {
throw std::runtime_error("无法重新打开日志文件: " + logPath_);
}
logFile_ << "[" << getCurrentTime()
<< "][Logger][doRollover][tid=" << getThreadId()
<< "][INFO]日志文件回滚完成" << std::endl;
} catch (const std::exception &e) {
std::cerr << "日志回滚失败: " << e.what() << std::endl;
// 尝试重新打开原始文件
logFile_.open(logPath_, std::ios::out | std::ios::app);
}
}
std::string Logger::getCurrentTime() const {
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) %
1000;
std::tm tm;
#ifdef _WIN32
localtime_s(&tm, &time);
#else
localtime_r(&time, &tm);
#endif
std::ostringstream oss;
oss << std::put_time(&tm, "%Y-%m-%d %H-%M-%S") << "." << std::setfill('0')
<< std::setw(3) << ms.count();
return oss.str();
}
std::string Logger::levelToString(Level level) const {
switch (level) {
case Level::DEBUG:
return "DEBUG";
case Level::INFO:
return "INFO";
case Level::WARN:
return "WARN";
case Level::ERROR:
return "ERROR";
case Level::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string Logger::getThreadId() const {
std::ostringstream oss;
oss << std::this_thread::get_id();
return oss.str();
}
cpp
复制代码
/**
* @file main.cpp
* @brief 日志类测试程序
*/
#include "Logger.h"
// #include "testlog.hpp"
#include <chrono>
#include <thread>
#include <vector>
Logger *m_logger;
// 测试函数,模拟多线程日志写入
void testLogging(int threadId) {
for (int i = 0; i < 10; ++i) {
std::string string =
"调试信息:" + std::to_string(threadId) + std::to_string(i);
string = "调试信息: 线程 " + std::to_string(threadId) + " - 日志消息" +
std::to_string(i);
if (i % 3 == 0) {
string = "警告: 线程 " + std::to_string(threadId) + " - 遇到特殊情况 ";
LOGW(string);
}
if (i == 7) {
string = "Error: " + std::to_string(threadId) + " - error message ";
LOGI(string);
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
std::string string = "线程 " + std::to_string(threadId) + " - 完成 ";
LOGI(string);
}
int main() {
m_logger = new Logger();
std::cout << "开始测试日志系统..." << std::endl;
// 初始化日志系统
m_logger->init("logs/app.log", Logger::Level::DEBUG);
std::cout << "日志系统初始化完成" << std::endl;
std::cout << "当前日志等级: " << static_cast<int>(m_logger->getLevel())
<< std::endl;
// 测试不同等级的日志
LOGD("这是一条调试日志");
LOGI("这是一条信息日志");
LOGW("这是一条警告日志");
LOGE("这是一条错误日志");
LOGF("这是一条致命错误日志");
// 测试多线程日志
std::cout << "\n开始多线程日志测试..." << std::endl;
std::vector<std::thread> threads;
for (int i = 0; i < 3; ++i) {
threads.emplace_back(testLogging, i + 1);
}
// 等待所有线程完成
for (auto &t : threads) {
t.detach();
}
std::cout << "多线程日志测试完成" << std::endl;
// 测试日志等级设置
std::cout << "\n测试日志等级设置..." << std::endl;
m_logger->setLevel(Logger::Level::WARN);
LOGD("这条调试日志应该不会出现");
LOGI("这条信息日志应该不会出现");
LOGW("这条警告日志应该出现");
LOGE("这条错误日志应该出现");
// 恢复日志等级
m_logger->setLevel(Logger::Level::DEBUG);
// 测试大文件回滚
std::cout << "\n测试日志回滚功能..." << std::endl;
std::cout << "写入大量日志以触发回滚..." << std::endl;
for (int i = 0; i < 1000; ++i) {
std::string string = "测试回滚的日志消息 " + std::to_string(i);
LOGI(string);
}
// TestLog test;
// test.test(1, 12);
std::cout << "日志回滚测试完成" << std::endl;
// 关闭日志系统
m_logger->shutdown();
std::cout << "\n日志系统已关闭" << std::endl;
std::cout << "\n测试完成!请检查 logs/app.log 文件查看日志输出" << std::endl;
std::cout << "日志格式: [YYYY-MM-DD "
"hh-mm-ss][文件名][函数名][tid=线程id][日志等级]日志信息"
<< std::endl;
return 0;
}