news 2026/1/16 4:30:19

C++笔记:流式异步日志库

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++笔记:流式异步日志库

综合我之前学过的异步日志库,流的缓冲区以及TensorRT里sample的日志设计。总结出了一套流式异步日志。

参考文章:

C++笔记:实现小型日志系统-CSDN博客

TensorRT笔记(2):解析样例中Logger日志类的设计-CSDN博客

C++笔记:std::stringbuf_修改std::string的缓存区-CSDN博客

异步日志

这部分和小型日志系统那块基本一样

//MyLogger.h #pragma once #include<thread> #include <mutex> #include <condition_variable> #include <atomic> #include <queue> #include<iostream> #include<fstream> #include<sstream> #include<chrono> #include <map> enum class LogLevel { INFO, DEBUG, WARN, ERR, }; class LogQueue { public: void push(const std::string& msg); bool pop(std::string& msg); void shutdown();//关闭 private: std::queue<std::string> queue; std::mutex mtx; std::condition_variable cond_var; std::atomic<bool> is_shutdown = false; }; class LogSystem { public: static LogSystem& GetInstance() { static LogSystem instance; return instance; } ~LogSystem(); void log(const LogLevel& level, const std::string msg) { //加入时间 auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::tm tm; localtime_s(&tm, &now); std::ostringstream ts; ts << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); //根据格式化,构造写入的字符串 std::string result = "[" + ts.str() + "][" + level_string[level] + "] " + msg; log_queue.push(result); } private: LogSystem(); LogSystem(const LogSystem&) = delete; LogSystem& operator=(const LogSystem&) = delete; std::map<LogLevel, std::string> level_string; LogQueue log_queue; std::thread work_thread; std::ofstream log_file; std::atomic<bool> exit_flag=false; inline static const char* filename = "Log.txt"; };
//MyLogger.cpp #include "MyLogger.h" void LogQueue::push(const std::string& msg) { { std::lock_guard<std::mutex> lock(mtx); if (is_shutdown) { throw std::runtime_error("LogQueue has been shut down!"); } queue.push(msg); } cond_var.notify_one(); } bool LogQueue::pop(std::string& msg) { { std::unique_lock<std::mutex> lock(mtx); cond_var.wait(lock, [this]() { return !queue.empty() || is_shutdown; }); if (is_shutdown && queue.empty()) return false; msg = queue.front(); queue.pop(); } return true; } void LogQueue::shutdown() { is_shutdown = true; cond_var.notify_all(); } LogSystem::~LogSystem() { exit_flag = true; log_queue.shutdown(); if (work_thread.joinable()) { work_thread.join(); } if (log_file.is_open()) { log_file.close(); } } LogSystem::LogSystem() :log_file(filename, std::ios::out | std::ios::app) { if (!log_file.is_open()) { throw std::runtime_error("Failed to open log file"); } level_string = { {LogLevel::INFO,"info"}, {LogLevel::DEBUG,"DEBUG"}, {LogLevel::WARN,"WARN"}, {LogLevel::ERR,"ERROR"} }; work_thread = std::thread([this]() { std::string msg; while (log_queue.pop(msg)) { //这里就不打换行了,默认流里面会有 log_file << msg << std::flush; } }); }

流式设计

这一块种缓冲区和stringbuf里的示例也基本一样,流的设计参考TensorRT的设计

class LogBuffer :public std::stringbuf { public: explicit LogBuffer(LogLevel level):mLevel(level) { } ~LogBuffer() { if (pbase() != pptr()) { putOutput(); } } int sync()override { putOutput(); return 0; } private: LogBuffer(const LogBuffer&) = delete; LogBuffer& operator=(const LogBuffer&) = delete; void putOutput() { //关键,这里丢给异步日志 LogSystem::GetInstance().log(mLevel, this->str()); this->str(""); } LogLevel mLevel; }; class LogStream :public std::ostream { public: explicit LogStream(LogLevel level) : std::ostream(nullptr), buf(level) { rdbuf(&buf); } private: LogBuffer buf; }; //最后定义了这四个日志等级的宏 #define LOG_INFO LogStream(LogLevel::INFO) #define LOG_DEBUG LogStream(LogLevel::DEBUG) #define LOG_WARN LogStream(LogLevel::WARN) #define LOG_ERR LogStream(LogLevel::ERR)

rdbuf

// 获取当前 streambuf std::streambuf* rdbuf() const; // 设置新的 streambuf,返回旧的 std::streambuf* rdbuf(std::streambuf* sb);

使用测试

int main() { auto f = []() { LOG_INFO << "hello" << 1 << std::endl; }; std::thread t1(f), t2(f); t1.join(); t2.join(); }
[2025-12-14 16:57:09][info] hello1 [2025-12-14 16:57:09][info] hello1

设计思想

为什么要做成流式?

这个其实没有为什么,用C++笔记:实现小型日志系统-CSDN博客里面的函数接口依然能做到异步日志。做成流只是为了好看。

当然实际上还是方便了一点,不用每次都选择日志等级,而是直接使用对应的宏即可。

并且如果要获取__FILE__,__LINE__等信息,宏因为不会设计函数调用过程,也更精准。

宏的设计

这里比较关键,我们写的这几个宏,对应的日志流对象都是临时的。作用域结束后/或者接收了std::endl等,就会把内容丢到日志队列里去。

为什么不做成全局的日志对象

这是最关键的问题。答案是不能这样做。

如果做成像std::cout那样的全局对象。那么多个线程同时用流的方式往里写,就会出问题。因为都是对同一个流对象写,那就会出现混乱?

对<<操作加锁?就像TensorRT那样。答案也是不行。因为<<加锁只能保证一次<<的原子性。不能保证多线程的顺序性,多线程之间的数据还是会混杂。

如果要对整个流对象加锁,那反而丢失了性能,完全不如临时流对象的效果。临时流对象之间是没有任何约束的,只有在push到日志队列的时候会竞争一下队列的锁。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/3 14:23:54

005-AES:采招网

本文来做一个标准AES案例&#xff1a;采招网 找加密参数 这里有一个响应是密文&#xff0c;今天来解密响应内容&#xff1a; 找解密位置 试过hook&#xff0c;直接pass掉&#xff0c;因为鼠标一移动到页面上就会断下来&#xff0c;可以试试再加些条件来判断&#xff08;类似条…

作者头像 李华
网站建设 2026/1/14 23:45:22

基于python+django的在线考试系统(源码+lw+部署文档+讲解等)

课题介绍本课题聚焦传统线下考试组织繁琐、阅卷效率低、成绩统计不便的痛点&#xff0c;设计并开发基于PythonDjango的在线考试系统。系统以Python作为核心开发语言&#xff0c;依托Django框架搭建高效稳定的后端服务架构&#xff0c;负责处理多角色权限管控、题库管理、试卷生…

作者头像 李华
网站建设 2026/1/12 19:16:56

C语言一维与二维数组名详解:从本质理解到高手应用

在C语言中&#xff0c;数组名看似简单&#xff0c;却是许多初学者容易混淆的重点和难点。理解数组名的本质&#xff0c;是掌握C语言数组编程的关键一步。数组是C语言中最基础且重要的数据结构之一&#xff0c;而数组名作为数组的标识符&#xff0c;其背后隐藏的语义和特性对于初…

作者头像 李华
网站建设 2026/1/6 2:38:20

当水印遇见AI:一场像素级的美学修复之旅

当水印遇见AI&#xff1a;一场像素级的美学修复之旅 【免费下载链接】IOPaint 项目地址: https://gitcode.com/GitHub_Trending/io/IOPaint 那张珍藏多年的老照片&#xff0c;右下角却印着碍眼的网站标识&#xff1b;精心收藏的漫画插图&#xff0c;被版权水印破坏了整…

作者头像 李华
网站建设 2026/1/7 6:49:09

软件测试是保障软件质量的关键环节,尤其在当前无法完全依赖形式化方法证明软件正确性的背景下,测试成为发现缺陷最主要、最有效的手段

一、前文铺垫中的任务管理部件与数据管理部件&#xff0c;构成了软件系统运行的基础支撑环境。任务管理部件通过识别事件驱动、时钟驱动及优先级相关的任务&#xff0c;确保系统行为的有序性和实时性&#xff1b;而数据管理部件则屏蔽底层存储差异&#xff0c;提供统一的数据访…

作者头像 李华
网站建设 2026/1/14 13:19:10

如何用AI快速生成Flink面试题答案?

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个AI辅助工具&#xff0c;能够根据用户输入的Flink面试题自动生成详细的解答。解答应包括&#xff1a;1. 问题分析&#xff1b;2. 核心概念解释&#xff1b;3. 代码示例&…

作者头像 李华