基于C++实现工业级线程安全日志系统
在服务端开发级中小型应用中稳定、易用、带自动切割与过期清理的日志模块是必需的本文基于C17及以上标准实现一款单例模式、线程安全、控制台彩色输出、按时间/大小自动切分、过期日志自动清理的企业级日志系统代码可直接集成到项目中使用。一、日志系统作用日志系统就是把程序运行过程中发生的所有事情记录下来控制台打印存本地文件。那么日志到底有什么用1.排查Bug程序崩溃、报错、卡死、逻辑错误不可能时刻盯着代码运行检查。例如数据库连接不上接口请求失败逻辑异常如果没有日志那就是瞎猜根本不知道错在哪里有日志直接定位到文件、哪一行、哪个函数一目了然很方便的就能定位错误。2.记录运行状态程序干了什么全部留下记录。例如程序什么时候启动什么时候关闭是否初始化成功后期维护、交接、复盘全靠日志3.线上不能打断点调试本地开发可以debug、打断点但是在服务器或者线上环境不能调试、不能打断点。线上出问题唯一能看的只有日志文件。4.安全审计、问题追溯谁操作了什么什么时候执行的如果出了事故通过日志可以追溯原因、定责、复盘5.长期保存不会丢失控制台打印程序一关记录全没所以日志通常写在文件里过期自动清理几天半个月的记录随时能翻看。二、日志系统要求企业对日志有不同要求整体大致是相同的。例如日志系统要求如下要求1日志存放位置默认为系统根目录的logs日志类里可以定义但可以支持存储到指定文件统一存储服务器。要求2日志文件名格式系统名-子系统名如果没有则默认为x-一级模块名-年月日.txt/.log要求3日志内容格式日志内容格式[时间] [日志级别] [程序文件名称/模块名称][类名-函数名/线程名/函数名/其他] [扩展内容可自定义比如子系统编号] : 内容。egcrm-x-jcrmh_payroll-2026-4-21-16.txt/.logcrm-123456-payroll-2026-4-21-16.txt/.logegcrm-x-jcrmh_payroll-2026-4-21-16.txt/.logcrm-123456-payroll-2026-4-21-16.txt/.log要求4日志支持大小上限过大造成读写操作消耗太多资源比如大小默认1个G大于则备份为文件名-数字序号。egcrm-x-jcrmh_payroll-2026-4-21-16-1.txt/.log要求5日志支持有效期用于只保存近期日志比如默认15天超过有效期就清理。三、核心设计目标单例模式全局唯一实例避免多日志对象冲突线程安全多线程并发写日志不错乱彩色控制台输出不同级别日志区分颜色便于调试文件持久化自动创建目录按小时生成日志文件大小切割单个日志文件超出上限自动备份避免读写资源操作消耗资源过期清理启动时自动删除超过指定天数的历史日志格式化输出支持可变参数高容错日志模块不影响主程序运行四、整体架构组件核心类与功能划分Logger单例类日志洗头工的核心负责控制台输出、文件写入、日志切割、过期清理日志级别定义8级日志INFO/DEBUG/NOTICE/WARNING/ERROR/CRITICAL/ALERT/EMERGENCY宏定义封装简化调用自动携带文件名、函数名等。关键技术点单例模式线程安全的懒汉模式std::mutex保证多线程日志写入安全C17 std::filesystem跨平台文件/目录操作可变参数va_list实现格式化日志输出控制台ANSI颜色码跨平台彩色输出可变参数va_list实现格式化日志输出五、日志代码实现Logger.h#pragma once #includeiostream #includestring #includecstdarg #includestdio.h #includechrono #includemutex #includefstream #includefilesystem namespace fs std::filesystem; // eg: LOG_INFO(子系统编号,日志内容) #define LOG_INFO(extendId,format,...)\ Logger::getInstance().log(INFO,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_DEBUG(extendId,format,...)\ Logger::getInstance().log(DEBUG,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_NOTICE(extendId,format,...)\ Logger::getInstance().log(NOTICE,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_WARNING(extendId,format,...)\ Logger::getInstance().log(WARNING,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_ERROR(extendId,format,...)\ Logger::getInstance().log(ERROR,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_CRITICAL(extendId,format,...)\ Logger::getInstance().log(CRITICAL,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_ALERT(extendId,format,...)\ Logger::getInstance().log(ALERT,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_EMERGENCY(extendId,format,...)\ Logger::getInstance().log(EMERGENCY,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) // 定义日志级别 enum LogLevel { INFO, // 普通信息有意义的事件 DEBUG, // 调式信息详细的调试信息 NOTICE, // 提示信息正常但是值得注意的事件 WARNING, // 警告信息异常事件但是并不是错误 ERROR, // 错误信息运行时的错误不需要立即注意到但是需要被专门记录并监控到 CRITICAL, // 严重信息边界条件 ALERT, // 警报信息必须立即采取行动 EMERGENCY // 紧急请求系统不可用了 }; // 定义日志内容颜色全局为绿色日志级别为高亮色 #define COLOR_RESET \033[0m // 重置 #define GREEN \033[32m // 整体绿色 #define COLOR_INFO \033[1;34m // 亮蓝 #define COLOR_DEBUG \033[1;37m // 亮白 #define COLOR_NOTICE \033[1;36m // 亮青 #define COLOR_WARNING \033[1;33m // 亮黄 #define COLOR_ERROR \033[1;31m // 亮红 #define COLOR_CRITICAL \033[1;35m // 亮紫 #define COLOR_ALERT \033[1;30m // 亮黑色 #define COLOR_EMERGENCY \033[1;41m // 红底高亮 // 日志全局可配置 #define LOG_SYSTEM_NAME crm // 系统名称 #define LOG_SUB_SYSTEM_NAME x // 子系统名称没有则填x #define LOG_MODULE_NAME jrcmh_payroll // 一级模块名称 #define LOG_DIR ./logs/ // 日志默认存放位置可自定义修改存储位置 #define LOG_MAX_SIZE (1024*1024*1024) // 单个日志文件最大大小默认1GB #define LOG_EXPIRE_DAYS 15 // 日志保留天数超过默认天数15天自动删除 // 日志类实现为单例 class Logger { public: static Logger getInstance(); // 获取日志唯一实例 std::string getNowTime(); // 获取人类可读的当前时间年-月-日 时:分:秒) std::string levelToString(LogLevel logLevel); // 日志级别转字符串 std::string getLevelColor(LogLevel loglevel); // 获取日志级别颜色 void log(LogLevel level, const char* file, const char* func, const std::string extendId, const char* format, ...); // 写日志 Logger(const Logger) delete; Logger operator(const Logger) delete; private: Logger(std::string sysName, std::string subSysName, std::string moduleName, std::string logDir, size_t maxSize, int expireDays); void createDir(const std::string dir); // 创建日志目录不存在则创建 void cleanExpiredLog(); // 清理过期日志超过指定天数默认15天自动删除 void consoleOutput(const std::string time, // 输出到控制台带颜色 const std::string level, const std::string color, const char* file, const char* func, const std::string extendId, const std::string content); void fileOutPut(const std::string time, // 输出到日志文件自动切割、自动清理过期日志 const std::string level, const char* file, const char* func, const std::string extendId, const std::string content); std::string generateLogFileName(); // 生成今天/当前小时的日志文件名 void rotateLogFile(); // 日志文件切割超过大小自动备份 std::mutex mutex_; std::string sysName_; // 系统名 std::string subSysName_; // 子系统名 std::string moduleName_; // 模块名 std::string logDir_; // 日志存放目录 size_t maxSize_; // 单个日志文件最大大小 int expireDays_; // 日志有效期 std::ofstream ofs_; std::string currentFile_; // 当前正在写入的日志文件名 };Logger.cpp#define _CRT_SECURE_NO_WARNINGS #includeLogger.h Logger::Logger(std::string sysName, std::string subSysName, std::string moduleName, std::string logDir, size_t maxSize, int expireDays) :sysName_(sysName), subSysName_(subSysName), moduleName_(moduleName), logDir_(logDir), maxSize_(maxSize), expireDays_(expireDays) { createDir(logDir_); // 初始化时创建logs目录 cleanExpiredLog(); // 初始化时自动清理有效期的旧日志 } Logger Logger::getInstance() { static Logger logger( LOG_SYSTEM_NAME, LOG_SUB_SYSTEM_NAME, LOG_MODULE_NAME, LOG_DIR, // 默认日志存放目录程序根目录 / logs LOG_MAX_SIZE, LOG_EXPIRE_DAYS); return logger; } std::string Logger::getNowTime() { time_t now time(0); struct tm* time_t localtime(now); char buffer[128] { 0 }; snprintf(buffer, sizeof(buffer), %4d-%02d-%02d %02d:%02d:%02d, time_t-tm_year 1900, time_t-tm_mon 1, time_t-tm_mday, time_t-tm_hour, time_t-tm_min, time_t-tm_sec); return buffer; } std::string Logger::levelToString(LogLevel logLevel) { switch (logLevel) { case(INFO): return INFO; break; case(DEBUG): return DEBUG; break; case(NOTICE): return NOTICE; break; case(WARNING): return WARNING; break; case(ERROR): return ERROR; break; case(CRITICAL): return CRITICAL; break; case(ALERT): return ALERT; break; case(EMERGENCY): return EMERGENCY; break; default: return UNKNOWN; } } std::string Logger::getLevelColor(LogLevel loglevel) { switch (loglevel) { case(INFO): return COLOR_INFO; break; case(DEBUG): return COLOR_DEBUG; break; case(NOTICE): return COLOR_NOTICE; break; case(WARNING): return COLOR_WARNING; break; case(ERROR): return COLOR_ERROR; break; case(CRITICAL): return COLOR_CRITICAL; break; case(ALERT): return COLOR_ALERT; break; case(EMERGENCY): return COLOR_EMERGENCY; break; default: return COLOR_RESET; } } // 日志内容格式[时间][日志级别][文件路径][函数名][扩展编号]:内容 // eg[2026-4-17 16:49:32][INFO][mian.cpp][main][1001]:服务启动成功 void Logger::log(LogLevel level, const char* file, const char* func, const std::string extendId, const char* format, ...) { std::lock_guardstd::mutex lock(mutex_); // 1.生成日志内容 std::string timeStr getNowTime(); std::string levelStr levelToString(level); std::string levelColor getLevelColor(level); // 2.格式化日志内容 char buffer[1024] { 0 }; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); std::string content buffer; // 3.输出到控制台带颜色 consoleOutput(timeStr, levelStr, levelColor, file, func, extendId, content); // 4.输出到文件不带颜色纯文本 fileOutPut(timeStr, levelStr, file, func, extendId, content); } void Logger::createDir(const std::string dir) { if (!fs::exists(dir)) { fs::create_directories(dir); } } void Logger::cleanExpiredLog() { try { // 遍历logs目录下所有文件 for (auto p : fs::directory_iterator(logDir_)) { // 只处理普通文件 if (!p.is_regular_file()) continue; // 获取文件最后修改时间 auto lastWrite fs::last_write_time(p); // 获取文件当前时间 auto now fs::file_time_type::clock::now(); // 计算小时差 auto hours std::chrono::duration_caststd::chrono::hours(now - lastWrite).count(); // 转成天数 int days hours / 24; // 超过有效期删除文件 if (days expireDays_) fs::remove(p); } } catch (...) { //异常不处理防止日志模块崩溃影响主程序 } } void Logger::consoleOutput(const std::string time, const std::string level, const std::string color, const char* file, const char* func, const std::string extendId, const std::string content) { std::cout GREEN; // 全局绿色 std::cout [ time ]; std::cout [ color level GREEN]; std::cout [ file ] [ func ]; if (!extendId.empty()) { std::cout [ extendId ]; } std::cout : content COLOR_RESET std::endl; } void Logger::fileOutPut(const std::string time, const std::string level, const char* file, const char* func, const std::string extendId, const std::string content) { // 1.生成今天/当前小时的日志文件名 std::string newFile generateLogFileName(); if (currentFile_ ! newFile || !ofs_.is_open()) // 如果文件切换了或者文件没打开则重新打开新文件 { if (ofs_.is_open()) ofs_.close(); currentFile_ newFile; ofs_.open(currentFile_, std::ios::app); // 追加模式打开 } // 2.判断文件大小是否超过上限超过则切割备份 if (fs::exists(currentFile_) fs::file_size(currentFile_) maxSize_) { ofs_.close(); rotateLogFile(); // 文件重命名备份 ofs_.open(currentFile_, std::ios::app); // 重新创建一个空白的源文件名日志文件 } // 3.写入日志内容纯文本 ofs_ [ time ] [ level ] [ file ] [ func ]; if (!extendId.empty()) { ofs_ [ extendId ]; } ofs_ : content std::endl; // 4.立即刷新到磁盘防止丢失 ofs_.flush(); } // 生成日志文件名 // 格式系统名-子系统名-模块名-年-月-日-时.log std::string Logger::generateLogFileName() { time_t now time(0); struct tm* time_t localtime(now); char name[256]; snprintf(name, sizeof(name), %s-%s-%s-%04d-%02d-%02d-%02d.log, sysName_.c_str(), subSysName_.c_str(), moduleName_.c_str(), time_t-tm_year 1900, time_t-tm_mon 1, time_t-tm_mday, time_t-tm_hour); return logDir_ / name; } // 日志文件切割超过大小自动备份 // egoa-web-org-2024-09-03-11.txt/.log - oa-web-org-2024-09-03-11-1.txt void Logger::rotateLogFile() { // 去掉后缀.log std::string base currentFile_.substr(0, currentFile_.find_last_of(.)); int index 1; std::string target; // 找一个不存在的序号 while (true) { target base - std::to_string(index) .log; if (!fs::exists(target)) break; index; } // 重命名备份 fs::rename(currentFile_, target); }六、核心功能讲解1.日志文件管理自动创建目录程序启动时自动创建./logs目录无需手动创建。按小时命名文件名格式系统名-子系统名-模块名-年月日-小时.log方便按时间检索。按大小切割文件超出上限自动重命名备份。2.过期日志自动清理程序初始化时自动遍历logs日志目录计算天数自动删除超过保留天数的日志文件避免日志占用大量磁盘空间且清理逻辑带异常捕获日志模块不影响主程序。3.控制台彩色输出不同级别日志使用不同颜色便于调试。4.日志调用宏简化调用方式自动携带文件名、函数名等无需手动传递一行代码即可输出标准化日志。七、日志使用示例Test.cpp#define _CRT_SECURE_NO_WARNINGS #includeLogger.h int main() { LOG_INFO(1001, 记录工单查询数据: {\模块\:\工单\,\操作\:\查询\,\部门名称\:\123456\,\用户名\:\市长热线\,\工号\:\5600\}); LOG_WARNING(1002, 连接失败%s, 网络超时); LOG_DEBUG(1003, 调试信息: %s, 详细的调试信息); LOG_NOTICE(1004, 提示信息: %s, 正常,但是值得注意); LOG_ERROR(1005, 错误信息: %s, 运行时的错误需要被记录); LOG_CRITICAL(1006, 严重信息); LOG_ALERT(1007, 警报信息); LOG_EMERGENCY(1008, 紧急请求: %s, 系统不可用了); return 0; }八、日志使用测试测试大小切割超过单个日志文件大小上限则重命名备份。单个日志文件大小为1024字节时#define LOG_MAX_SIZE 1024写入大小未超过上限8条记录正常写入文件不备份。第二次写入时先判断日志大小是否超过上限先写入1条记录未上限写完成此时大小已经上限但是已经将这条记录写完再写第2条记录时大小超过上限执行切割重命名备份将原文件里的9条记录全部备份到-1.log文件再创建一个空白的原文件名日志文件继续写写完剩余7条记录。本文实现的 C 日志系统兼顾了易用性、稳定性、高性能完全满足工业级项目的日志需求。代码结构清晰、无第三方依赖、跨平台兼容可直接集成到各类 C 项目中省去重复造轮子的成本。九、日志扩展优化1、增加日志级别过滤只输出指定级别以上的日志。2、实现异步日志将写入操作放到后台线程提升性能。3、支持配置文件加载动态修改日志路径、大小、保留天数。4、增加日志压缩功能减少磁盘占用。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2543615.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!