C++通用日志模块

article/2025/9/6 18:12:18

概述

在 C++ 项目中开发时经常需要日志模块,为了不引入其它第三方日志模块包的基础上,基于标准的 C++17 的基础自己封装了一个日志模块

功能总结

  • 日志分等级(DEBUG / INFO / WARN / ERROR)
  • 支持日志文件轮转,自动备份旧日志
  • 彩色控制台输出
  • 线程安全(加锁)
  • 单例模式
  • 支持日志文件名带日期
  • 配置日志目录、文件名模板、等级、最大大小、保留个数

代码

#ifndef LOGMANAGER_H
#define LOGMANAGER_H#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <filesystem>
#include <chrono>
#include <ctime>
#include <mutex>
#include <iomanip>namespace fs = std::filesystem;// 定义日志等级
enum class LogLevel {DEBUG,  // 调试信息INFO,   // 普通信息WARN,   // 警告信息ERROR   // 错误信息
};// 日志管理器单例类
class LogManager {
public:// 获取单例实例static LogManager& getInstance() {static LogManager instance;return instance;}// 禁止拷贝构造和赋值LogManager(const LogManager&) = delete;LogManager& operator=(const LogManager&) = delete;// 配置日志参数void configure(const std::string& logDir = "logs",                     // 日志目录const std::string& filenamePattern = "logs_{time}.log", // 日志文件名格式,{time}会替换成日期LogLevel level = LogLevel::INFO,                        // 日志等级std::size_t maxSize = 10 * 1024 * 1024,                 // 最大文件大小,默认10MBint backupCount = 6) {                                  // 最大保留备份数std::lock_guard<std::mutex> lock(m_mutex);if (m_configured) return;  // 防止重复配置m_configured = true;m_logDir = logDir;m_filenamePattern = filenamePattern;m_logLevel = level;m_maxSize = maxSize;m_backupCount = backupCount;// 如果日志目录不存在则创建if (!fs::exists(m_logDir)) {fs::create_directories(m_logDir);}openLogFile(); // 打开日志文件}// 写入日志void log(LogLevel level, const std::string& message, const char* file, int line) {if (level < m_logLevel) return; // 如果日志等级不满足,直接返回std::lock_guard<std::mutex> lock(m_mutex);rotateIfNeeded(); // 检查是否需要日志轮转// 获取日志等级字符串std::string levelStr = getLevelString(level);// 获取当前时间字符串std::string timeStr = getCurrentTimeString();// 写入日志文件m_logFile << timeStr << " [" << levelStr << "] "<< fs::path(file).filename() << ":" << line<< " - " << message << std::endl;// 控制台彩色输出std::string colorCode = getColorCode(level);std::string resetCode = "\033[0m";std::cout << colorCode<< timeStr << " [" << levelStr << "] "<< fs::path(file).filename() << ":" << line<< " - " << message<< resetCode << std::endl;}private:// 私有构造函数,保证单例模式LogManager() : m_configured(false) {}// 获取日志等级对应的终端颜色代码std::string getColorCode(LogLevel level) {switch (level) {case LogLevel::DEBUG: return "\033[36m"; // 青色case LogLevel::INFO:  return "\033[32m"; // 绿色case LogLevel::WARN:  return "\033[33m"; // 黄色case LogLevel::ERROR: return "\033[31m"; // 红色}return "\033[0m"; // 默认颜色}// 打开日志文件void openLogFile() {std::string timeStr = getCurrentTimeStringForFilename(); // 获取当前日期字符串std::string filename = m_filenamePattern;size_t pos = filename.find("{time}");if (pos != std::string::npos) {filename.replace(pos, 6, timeStr); // 替换文件名中的{time}}m_logFilePath = fs::path(m_logDir) / filename;m_logFile.open(m_logFilePath, std::ios::app); // 追加方式打开}// 判断文件大小,必要时执行日志轮转void rotateIfNeeded() {if (fs::exists(m_logFilePath) &&fs::file_size(m_logFilePath) >= m_maxSize) {m_logFile.close();// 依次重命名旧文件,从最大备份编号往回推for (int i = m_backupCount - 1; i >= 0; --i) {std::string oldName = m_logFilePath.string() + "." + std::to_string(i);std::string newName = m_logFilePath.string() + "." + std::to_string(i + 1);if (fs::exists(oldName)) {fs::rename(oldName, newName);}}// 当前日志文件改名为 .0fs::rename(m_logFilePath, m_logFilePath.string() + ".0");openLogFile(); // 新建日志文件}}// 获取日志等级对应的字符串std::string getLevelString(LogLevel level) {switch (level) {case LogLevel::DEBUG: return "DEBUG";case LogLevel::INFO:  return "INFO";case LogLevel::WARN:  return "WARN";case LogLevel::ERROR: return "ERROR";}return "UNKNOWN";}// 获取当前时间字符串,格式:2025-05-29 20:15:03.123std::string getCurrentTimeString() {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::ostringstream oss;oss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S")<< "." << std::setfill('0') << std::setw(3) << ms.count();return oss.str();}// 获取当前日期字符串,供文件名使用,格式:2025-05-29std::string getCurrentTimeStringForFilename() {auto now = std::chrono::system_clock::now();auto time = std::chrono::system_clock::to_time_t(now);std::ostringstream oss;oss << std::put_time(std::localtime(&time), "%Y-%m-%d");return oss.str();}// 成员变量定义bool m_configured;                  // 是否已配置std::string m_logDir;               // 日志目录std::string m_filenamePattern;      // 日志文件名格式LogLevel m_logLevel;                // 日志等级std::size_t m_maxSize;              // 单个日志最大大小int m_backupCount;                  // 备份日志个数std::ofstream m_logFile;            // 日志文件输出流fs::path m_logFilePath;             // 当前日志文件路径std::mutex m_mutex;                 // 互斥锁,线程安全
};// 定义日志宏,自动记录当前文件名和行号
#define LOG_DEBUG(msg) LogManager::getInstance().log(LogLevel::DEBUG, msg, __FILE__, __LINE__)
#define LOG_INFO(msg)  LogManager::getInstance().log(LogLevel::INFO,  msg, __FILE__, __LINE__)
#define LOG_WARN(msg)  LogManager::getInstance().log(LogLevel::WARN,  msg, __FILE__, __LINE__)
#define LOG_ERROR(msg) LogManager::getInstance().log(LogLevel::ERROR, msg, __FILE__, __LINE__)#endif // LOGMANAGER_H

运行示例


#include "LogManager.h"int main() {// 配置日志系统,只需要调用一次LogManager::getInstance().configure("logs",                     // 日志目录"app_log_{time}.log",       // 日志文件名模板,{time} 会被替换成日期LogLevel::DEBUG,            // 最低日志等级(DEBUG/INFO/WARN/ERROR)1024 * 1024,                // 日志文件最大 1MB(测试方便改小一点)3                           // 最多保留 3 个备份日志);// Log output example
LOG_DEBUG("This is a debug message");
LOG_INFO("This is an INFO level message");
LOG_WARN("This is a WARN level message");
LOG_ERROR("This is an ERROR level message");return 0;
}

在这里插入图片描述


http://www.hkcw.cn/article/aRrpLhrJcR.shtml

相关文章

Arbitrum Stylus 合约实战 :Rust 实现 ERC20

在《Arbitrum Stylus 深入解析与 Rust 合约部署实战》篇中&#xff0c;我们深入探讨了 Arbitrum Stylus 的核心技术架构&#xff0c;包括其 MultiVM 机制、Rust 合约开发环境搭建&#xff0c;以及通过 cargo stylus 实现简单计数器合约的部署与测试。Stylus 作为 Arbitrum Nitr…

ADQ36-2通道2.5G,4通道5G采样PXIE

ADQ36是一款高端12位四通道灵活数据采集板&#xff0c;针对高通道数科学应用进行了优化。ADQ36具有以下特性: 4 / 2模拟输入通道每通道2.5 / 5 GSPS7gb/秒的持续数据传输速率两个外部触发器通用输入/输出&#xff08;GPIO&#xff09;ADQ36数字化仪包括固件FWDAQ ADQ36简介 特…

20中数组去重的方法20种数组去重的方法

开始 本文有很多问题&#xff0c;并没有直接给出答案&#xff0c;大伙有自己思考的可以评论区留言。关于时间复杂度只是一个大体的估计。20种只能说保守了&#xff0c;20种都是单论思路而已&#xff0c;暂时没想到更多的思路&#xff0c;有其他方法的可以评论区留言。 easy模式…

工厂模式 vs 策略模式:设计模式中的 “创建者” 与 “决策者”

在日常工作里&#xff0c;需求变动或者新增功能是再常见不过的事情了。而面对这种情况时&#xff0c;那些耦合度较高的代码就会给我们带来不少麻烦&#xff0c;因为在这样的代码基础上添加新需求往往困难重重。为了保证系统的稳定性&#xff0c;我们在添加新需求时&#xff0c;…

Emacs 折腾日记(二十六)——buffer与窗口管理

本节我们将介绍如何在Emacs中的buffer与窗口管理&#xff0c;目标是快速管理窗口&#xff0c;以及快速在不同buffer中进行切换 基本概念介绍 Emacs与vim相比的一个特点是&#xff0c;Emacs是一个窗口程序&#xff0c;或者说是一个gui程序。而vim是一个终端字符界面程序(当然E…

强化学习(十三)DQN

传统的强化学习算法会使用表格的形式存储状态价值函数 V ( s ) V(s) V(s) 或动作价值函数 Q ( s ) Q(s) Q(s) &#xff0c;但是这样的方法存在很大的局限性。例如&#xff0c;现实中的强化学习任务所面临的状态空间往往是连续的&#xff0c;存在无穷多个状态&#xff0c;在这…

RapidOCR集成PP-OCRv5_det mobile模型记录

该文章主要摘取记录RapidOCR集成PP-OCRv5_mobile_det记录&#xff0c;涉及模型转换&#xff0c;模型精度测试等步骤。原文请前往官方博客&#xff1a; https://rapidai.github.io/RapidOCRDocs/main/blog/2025/05/26/rapidocr%E9%9B%86%E6%88%90pp-ocrv5_det%E6%A8%A1%E5%9E%8B…

【深度学习】13. 图神经网络GCN,Spatial Approach, Spectral Approach

图神经网络 图结构 vs 网格结构 传统的深度学习&#xff08;如 CNN 和 RNN&#xff09;在处理网格结构数据&#xff08;如图像、语音、文本&#xff09;时表现良好&#xff0c;因为这些数据具有固定的空间结构。然而&#xff0c;真实世界中的很多数据并不遵循网格结构&#x…

从“无差别降噪”到“精准语音保留”:非因果优化技术为助听设备和耳机降噪注入新活力

在复杂环境中保持清晰语音感知一直是助听设备与消费级耳机的核心挑战。传统主动降噪&#xff08;ANC&#xff09;技术虽能抑制环境噪声&#xff0c;但会无差别削弱所有声音&#xff0c;导致用户难以听清目标方向的语音&#xff08;如对话者&#xff09;。近年来&#xff0c;开放…

家庭路由器改装,搭建openwrt旁路由以及手机存储服务器,实现外网节点转发、内网穿透、远程存储、接入满血DeepSeek方案

大家好&#xff0c;也是好久没有发文了&#xff0c;最近在捣鼓一些比较有趣的东西&#xff0c;打算跟大家分享一下&#xff01; 先聊一下我的大致方案嘛&#xff0c;最近感觉家里路由器平时一直就只有无线广播供网的功能&#xff0c;感觉这么好的一下嵌入式设备产品不应该就干这…

【Linux】shell脚本的变量与运算

目录 一.变量 1.1什么是变量 1.2变量的命名 1.3变量的调用 1.4字符的转义 1.5变量的取消 二.变量的类型 2.1函数级变量 2.2环境级变量 2.3用户级变量 2.4系统级变量 2.5常见的系统变量 三..特殊变量及定义 3.1用命令的执行结果定义变量 3.2传参变量 3.3交互式传…

Linux进程概念

一.冯诺依曼体系结构 冯诺依曼体系结构是当代计算机的基本结构&#xff0c;它主要包括几个板块&#xff0c;输入设备&#xff0c;输出设备&#xff0c;存储器&#xff0c;运算器和控制器。 下面是简略版的图解析&#xff1a; 输入设备主要包含鼠标&#xff0c;键盘&#xff0…

[9-2] USART串口外设 江协科技学习笔记(9个知识点)

1 2 3 智能卡、IrDA和LIN是三种不同的通信技术&#xff0c;它们在电子和汽车领域中有着广泛的应用&#xff1a; • 智能卡&#xff08;Smart Card&#xff09;&#xff1a; • 是什么&#xff1a;智能卡是一种带有嵌入式微处理器和存储器的塑料卡片&#xff0c;可以存储和处理数…

低代码——表单生成器以form-generator为例

主要执行流程说明&#xff1a; 初始化阶段 &#xff1a; 接收表单配置对象formConf深拷贝配置&#xff0c;初始化表单数据和验证规则处理每个表单组件的默认值和特殊配置&#xff08;如文件上传&#xff09; 渲染阶段 &#xff1a; 通过render函数创建el-form根组件递归渲染表…

奥威BI+AI——高效智能数据分析工具,引领数据分析新时代

随着数据量的激增&#xff0c;企业对高效、智能的数据分析工具——奥威BIAI的需求日益迫切。奥威BIAI&#xff0c;作为一款颠覆性的数据分析工具&#xff0c;凭借其独特功能&#xff0c;正在引领数据分析领域的新纪元。 一、‌零报表环境下的极致体验‌ 奥威BIAI突破传统报表限…

grid网格布局

使用flex布局的痛点 如果使用justify-content: space-between;让子元素两端对齐&#xff0c;自动分配中间间距&#xff0c;假设一行4个&#xff0c;如果每一行都是4的倍数那没任何问题&#xff0c;但如果最后一行是2、3个的时候就会出现下面的状况&#xff1a; /* flex布局 两…

基于 GitLab CI + Inno Setup 实现 Windows 程序自动化打包发布方案

在 Windows 桌面应用开发中&#xff0c;实现自动化构建与打包发布是一项非常实用的工程实践。本文以我在开发PackTes项目时的为例&#xff0c;介绍如何通过 GitLab CI 配合 Inno Setup、批处理脚本、Qt 构建工具&#xff0c;实现版本化打包并发布到共享目录的完整流程。 项目地…

江西某石灰石矿边坡自动化监测

1. 项目简介 该矿为露天矿山&#xff0c;开采矿种为水泥用石灰岩&#xff0c;许可生产规模200万t/a&#xff0c;矿区面积为1.2264km2&#xff0c;许可开采深度为422m&#xff5e;250m。矿区地形为东西一北东东向带状分布&#xff0c;北高南低&#xff0c;北部为由浅变质岩系组…

星海掘金:校园极客的Token诗篇(蓝耘MaaS平台)——从数据尘埃到智能生命的炼金秘录

hi&#xff0c;我是云边有个稻草人 目录 前言 一、初识蓝耘元生代MaaS平台&#xff1a;零门槛体验AI服务 1.1 从零开始——平台注册与环境搭建 1.2 平台核心功能 1.3 蓝耘平台的优势在哪里&#xff1f; 二、知识库构建新篇章&#xff1a;从零碎资料到智能语义仓库的蜕变…

测试概念 和 bug

一 敏捷模型 在面对在开发项目时会遇到客户变更需求以及合并新的需求带来的高成本和时间 出现的敏捷模型 敏捷宣言 个人与交互重于过程与工具 强调有效的沟通 可用的软件重于完备的文档 强调轻文档重产出 客户协作重于合同谈判 主动及时了解当下的要求 相应变化…