当前位置: 首页 > article >正文

日志系统整体设计步骤以及功能函数梳理

首先到底要做一个什么东西我们要造一个C 高并发异步日志库功能如下用LOG_INFO xxx这种简单写法自动带时间、级别、文件名、函数名、行号支持级别过滤TRACE/DEBUG/INFO/WARN/ERROR/FATAL异步写文件不卡主线程文件自动滚动按大小 / 按天线程安全多线程一起写不乱你平时写的cout 出错了 endl;我们要做的LOG_INFO 服务器启动成功; LOG_ERROR 数据库连接失败;输出效果20260407 18:22:33.123456 INFO main.cpp:100 server start success第一阶段最基础的零件——所有功能的地基1. LogCommon.hpp 公用定义作用定规矩定义 6 个日志级别TRACE DEBUG INFO WARN ERROR FATAL定义级别名字符串映射INFO → INFO定义缓冲区大小常量namespace tulun { // 日志级别数字越小越详细 enum class LOG_LEVEL { TRACE 0, DEBUG, INFO, WARN, ERROR, FATAL, }; // 数字转字符串0→TRACE1→DEBUG… const char* LLtoStr[] { TRACE, DEBUG, INFO, WARN, ERROR, FATAL }; // 缓冲区大小常量 const int SMALL_BUFF_SIZE 128; }面试点日志级别用来过滤线上只开 INFO调试开 DEBUG。2. Timestamp 时间戳作用给每一条日志打精准时间功能获取当前微秒级时间格式化成好看的字符串20260407 18:22:33.123456给文件命名用20260407-182233核心函数now()获取当前时间toString()格式化字符串// 获取当前微秒时间戳 Timestamp Timestamp::Now() { struct timeval tv; gettimeofday(tv, nullptr); uint64_t micro tv.tv_sec * 1000000 tv.tv_usec; return Timestamp(micro); } // 转成 2026/04/07-15:30:55.123456Z string Timestamp::toFormattedString()为什么微秒高并发系统能区分同一毫秒内的多条日志。3. LogMessage 日志消息体作用把一条日志拼成完整格式它会自动拼接时间 级别 文件名 行号 你的内容你写LOG_INFO 连接成功;它自动拼成2026/04/07-15:30:55 INFO main.cpp main() 10 连接成功组成部分时间戳 Timestamp日志级别 INFO/DEBUG…文件名截取最后一段函数名__func__行号__LINE__用户输入的内容核心函数// 构造函数拼头部 LogMessage::LogMessage(level, file, func, line) { ss 时间 级别 文件名 函数 行号; header_ ss.str(); } // 重载 运算符让你能流式写 templateclass T LogMessage operator(const T val) { text_ val; return *this; } // 最终返回完整日志字符串 string LogMessage::toString() { return header_ text_; }第二阶段核心大脑 Logger——日志器作用给用户提供最简单的接口控制全局行为它干 5件事全局日志级别控制——判断这条日志该不该输出提供输出接口控制台 / 文件 / 异步——提供LOG_INFO等宏给你用提供刷新接口——决定输出到哪控制台 / 文件 / 网络线程安全控制析构时输出日志最关键的设计利用析构函数输出日志// 1. 用户写 LOG_INFO xxx Logger::Logger(level, file, func, line) : impl_(level, file, func, line) // 构造 LogMessage {} // 2. 临时对象销毁时自动输出 Logger::~Logger() { // 把日志字符串给输出函数 s_output_(impl_.toString()); s_flush_(); // 如果是 FATAL直接退出进程 if (level FATAL) exit(1); }日志宏#define LOG_INFO \ if (当前级别 INFO) \ Logger(INFO, __FILE__, __func__, __LINE__).stream()为什么要用宏自动带__FILE__文件名自动带__func__函数名自动带__LINE__行号级别不满足时整条语句不执行零开销最重要的 4 个函数setLogLevel设置过滤级别低于该级别直接丢弃。setOutput你可以指定输出位置默认输出到控制台可以改成输出到文件可以改成输出到异步系统output真正执行输出的函数。日志宏 LOG_XXX包装了文件、行号、函数名让你用起来超级简单。第三阶段同步日志流程超级简单LOG_INFO hello → 生成 Logger 临时对象 → 构造 LogMessage拼头部 → operator 写入内容 → 析构函数调用 output() → output 直接输出到控制台/文件缺点写磁盘是慢 IO高并发下业务线程会被日志卡住性能暴跌。所以我们必须做异步日志第四阶段高性能核心 —— 异步日志 AsyncLogging为什么要异步同步主线程自己写磁盘 →慢、阻塞异步主线程只丢到内存 →极速、不阻塞后台线程慢慢写磁盘异步日志设计思想双缓冲 / 多缓冲技术你可以理解为两个缓冲区交换使用前端缓冲区主线程拼命往里写日志后端缓冲区后台线程拿去写入文件当前端满了直接交换两个缓冲区后台线程默默写入完全不影响主线程这就是高并发日志库的核心类似于两个桶接力倒水前端桶主线程疯狂写日志后端桶后台线程拿去写文件前端满了 →交换两个桶主线程继续写后台线程慢慢刷盘关键成员currentBuffer_当前写的缓冲区buffers_装满的缓冲区队列mutex_保护缓冲区cond_通知后台线程thread后台写线程核心流程用户 append加锁 → 往 currentBuffer 写 → 满了就放入队列 → 唤醒后端后端线程 loop等待队列非空 →把缓冲区全部拿走→ 解锁 → 批量写文件核心函数// 主线程调用写入日志 void AsyncLogging::append(const char* msg, size_t len) { lock_guard(mutex); if (当前缓冲区不够放) { buffers.push_back(move(currentBuffer)); currentBuffer.reset(); } currentBuffer.append(msg, len); cond.notify_one(); } // 后台线程函数 void AsyncLogging::workThreadFunc() { vectorstring buffersToWrite; while (running) { { unique_lock(mutex); // 等待 200ms 或有数据 cond.wait_for(lock, 200ms); // 把当前缓冲也加入队列 buffers.push_back(move(currentBuffer)); // 交换后端拿走所有缓冲前端清空 buffersToWrite.swap(buffers); } // 无锁批量写文件性能关键点 for (auto buf : buffersToWrite) { output_.append(buf); } } }为什么这么设计快锁时间极短只在交换缓冲区时加锁批量写减少 IO 次数主线程几乎无等待异步日志 3 个核心函数append()主线程调用把日志塞缓冲区超快threadFunc()后台线程死循环等待缓冲区满交换缓冲区写入文件start() / stop()启动 / 停止后台线程第五阶段 线程同步工具CountDownLatchCountDownLatch.hpp——等线程启动好作用确保异步线程真正启动后主线程才继续跑用法初始化计数 1主线程调用wait()线程启动好调用countDown()主线程继续执行保证日志线程没启动前不写日志第六阶段文件写入模块——日志最终落地1. AppendFile 底层文件操作作用封装 fwrite自带缓冲区减少 IO它是真正把字节写到磁盘的类。成员FILE* fp_文件句柄buffer_自带 1MB 缓冲区writenBytes_统计已写字节数用来滚动文件// 追加写入自带缓冲超快 void AppendFile::append(const char* msg, size_t len) { fwrite_unlocked(msg, 1, len, fp_); writenBytes_ len; } // 刷新到磁盘 void AppendFile::flush() { fflush(fp_); }2. LogFile 文件管理器作用管理日志文件 → 满了就切到第二天就切我最爱的功能永不爆炸的日志文件它管 3 件事生成文件名包含时间、主机名、进程 ID判断是否要滚动文件线程安全的写入接口滚动规则按大小滚动默认 128KB超过就切按时间滚动每天 0 点自动切新文件每次写入都会检查是否满了 / 是否跨天核心函数// 新建一个日志文件 bool LogFile::rollFile() // 线程安全的追加 void LogFile::append(...) { lock_guard(mutex); append_unlocked(...); } // 不加锁的追加内部用 void LogFile::append_unlocked(...) { file_-append(...); // 检查是否需要滚动/刷新 if (写太多) rollFile(); if (太久没刷) flush(); }文件名示例mylog.20260407_153055.unknownhost.1234.log为什么要滚动日志答防止单文件过大无法打开、占用磁盘、方便清理归档。第七阶段 整个库完整运行流程这个日志系统的亮点多级日志过滤线上关闭调试日志微秒级时间戳高并发精准排序自动携带上下文文件、行号、函数异步多缓冲主线程无阻塞高性能日志文件滚动按大小 / 时间切分线程安全多线程并发写不乱码异步日志完整链路1. 用户调用 LOG_INFO hello 2. 宏判断级别满足才执行 3. 构造 Logger 对象 4. 构造 LogMessage拼接时间/级别/文件/行号 5. operator 填入用户内容 6. Logger 析构调用 output() 7. output 把日志交给 AsyncLogging.append() 8. 主线程写入前端缓冲 9. 缓冲满/超时 → 交换缓冲 10. 后台线程批量写入 LogFile 11. LogFile 调用 AppendFile 写磁盘 12. 自动检查文件大小/时间 → 滚动文件我现在用一句话给你串起来我先做了时间戳、级别定义、底层文件写入这些小零件然后做了日志消息打包、日志入口宏为了高性能做了异步多缓冲为了方便管理做了日志滚动最后拼成一个主线程无阻塞、多线程安全、自动切文件的工业级日志库。文件核心功能LogCommon.hpp定义日志级别枚举TRACE/DEBUG/INFO/WARN/ERROR/FATAL、日志级别字符串映射、缓冲区大小常量Timestamp.hpp/.cpp时间戳封装支持微秒级时间获取、格式化输出普通字符串 / 文件命名格式LogMessage.hpp/.cpp日志消息封装拼接日志头时间、级别、文件 / 函数 / 行号和日志内容Logger.hpp/.cpp日志器核心类提供日志宏LOG_TRACE/LOG_DEBUG 等、输出 / 刷新函数设置、日志级别控制AppendFile.hpp/.cpp底层文件写入封装支持大缓冲区、非阻塞写入、字节计数LogFile.hpp/.cpp日志文件管理支持文件滚动按大小 / 时间、线程安全写入、自动刷新AsynLogging.hpp/.cpp异步日志实现后台线程批量写入日志减少主线程阻塞CountDownLatch.hpp/.cpp倒计时锁用于异步日志线程初始化同步第八阶段面试官最爱问的 10 题1. 你们日志系统为什么用异步同步日志会阻塞业务线程高并发下性能极差。异步日志将 IO 交给后台线程主线程只写内存无阻塞、高吞吐。2. 异步日志怎么实现的采用多缓冲技术前端缓冲区接收日志后端线程负责写入缓冲区满或定时则交换缓冲区批量写入3. 日志级别有什么用用于过滤日志。线上环境只开启 INFO/WARN/ERROR减少日志量提升性能。4. 日志文件滚动是什么防止文件无限变大。按大小或时间自动创建新文件方便管理、压缩、清理。5. 日志系统线程安全吗安全。共享缓冲区加锁、文件写入加锁、异步线程单独处理 IO。6. 你们日志的性能为什么高异步非阻塞批量写入磁盘减少 IO缓冲区减少系统调用锁粒度小7. FATAL 日志做了什么打印错误信息然后直接终止进程用于严重错误。8. 日志为什么要带文件名和行号方便快速定位代码问题。9. 时间戳为什么用微秒高并发场景下同一毫秒可能产生大量日志微秒能区分顺序。10. 这套日志能用于生产环境吗完全可以。它是仿照 Muduo 日志库设计的工业级标准高并发稳定可靠。

相关文章:

日志系统整体设计步骤以及功能函数梳理

首先到底要做一个什么东西&#xff1f;我们要造一个 C 高并发异步日志库&#xff0c;功能如下&#xff1a;用 LOG_INFO << "xxx" 这种简单写法自动带&#xff1a;时间、级别、文件名、函数名、行号支持级别过滤&#xff08;TRACE/DEBUG/INFO/WARN/ERROR/FATAL&…...

HWD风速风向传感器Arduino驱动库详解

1. 项目概述 WindSensorHWD_asukiaaa 是一款专为 HWD 系列风速风向传感器设计的嵌入式驱动库&#xff0c;面向 Arduino 及兼容平台&#xff08;如 STM32、ESP32&#xff09;提供标准化、可移植的数据采集接口。该库并非通用串口协议解析器&#xff0c;而是深度适配日本 SigLab …...

evo实战:A-LOAM在KITTI数据集上的多维度性能剖析

1. 从KITTI到ROS&#xff1a;数据格式转换实战 第一次接触KITTI数据集时&#xff0c;我被它那庞大的.bin点云文件搞得一头雾水。作为一个常年和ROS打交道的工程师&#xff0c;我深知bag格式才是SLAM算法的"通用语言"。这里分享一个我验证过的高效转换方案——使用lid…...

软件工程导论简答题速查手册:高频考点+避坑指南(附PDF下载)

软件工程导论高频考点精粹&#xff1a;命题陷阱破解与记忆强化指南 面对软件工程导论考试中纷繁复杂的简答题&#xff0c;许多考生常陷入"知识点背了却不会答题"的困境。这份手册从历年真题大数据中提炼出最高频出现的50个核心考点&#xff0c;采用"命题视角记忆…...

【Hot 100 刷题计划】 LeetCode 45. 跳跃游戏 II | C++ 贪心算法最优解题解

LeetCode 45. 跳跃游戏 II | C 动态规划与贪心 O(N) 双解法题解 &#x1f4cc; 题目描述 题目级别&#xff1a;中等 给定一个长度为 n 的 0 索引整数数组 nums。初始位置在下标 0。 每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。 返回到达 n - 1 的 最小跳跃次数。测试用…...

【Dify】无网络环境下的Dify部署指南:从在线到离线的无缝迁移

1. 为什么需要离线部署Dify&#xff1f; 在企业级应用场景中&#xff0c;数据安全和网络隔离是刚需。很多金融、政务、医疗机构的服务器都部署在内网环境&#xff0c;完全与互联网物理隔离。这时候如果想使用Dify这样的AI应用开发平台&#xff0c;常规的在线安装方式就完全行不…...

002、现代Python后端开发环境与工具链搭建

002、现代Python后端开发环境与工具链搭建 上周排查一个线上问题&#xff0c;日志里报了个ImportError: cannot import name ... from partially initialized module。花了半小时才发现&#xff0c;是同事本地虚拟环境混用了Python 3.8和3.10的依赖&#xff0c;打包时没锁版本。…...

角色如何朝向最近的目标点

将所有目标点添加到数组获取最近的目标...

单线级联可寻址七段数码管设计

1. 项目概述可寻址七段数码管显示模块&#xff08;Addressable Seven Segment Display&#xff09;是一种突破传统驱动架构的嵌入式显示解决方案。其核心设计目标是&#xff1a;仅需单根 GPIO 引脚&#xff0c;即可级联驱动任意数量的七段数码管单元。该方案彻底摒弃了传统数码…...

嵌入式C轻量序列化库:结构体打包与位操作零依赖实现

1. 项目概述dot_util是一个轻量级、零依赖的嵌入式 C 语言工具库&#xff0c;专为资源受限的 MCU&#xff08;如 Cortex-M0/M3/M4、RISC-V 32 位内核&#xff09;设计。其核心定位并非通用算法库或 HAL 封装&#xff0c;而是聚焦于底层数据序列化与结构体操作的工程痛点&#x…...

深入解析CAN报文中的Motorola字节排序:MSB与LSB的实战对比

1. 从汽车仪表盘说起&#xff1a;为什么需要了解CAN字节排序 去年调试一辆新能源车的仪表盘时&#xff0c;我遇到了一个诡异现象&#xff1a;车速显示在80km/h时突然跳变成20km/h。排查三天后发现&#xff0c;问题出在CAN报文解析时搞混了Motorola的MSB和LSB排序方式。这个经历…...

LeetCode--344.反转字符串(字符串/双指针法)

344.反转字符串 题目描述 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。 示例 1&#xff1a; 输入&#x…...

SAP BP创建供应商主数据保姆级教程:从分组Z005到统驭科目2241039801的完整配置流程

SAP BP供应商主数据创建实战指南&#xff1a;从分组配置到统驭科目设置的深度解析 在SAP系统中&#xff0c;供应商主数据的准确创建是财务和采购业务流程的基石。不同于传统的供应商创建方式&#xff0c;BP&#xff08;Business Partner&#xff09;事务码提供了一种更为统一和…...

大麦APP抢票协议分析:从‘掌密网络’代码看移动端API安全防护

大麦APP抢票协议安全防护体系深度解析 1. 移动端API安全防护的现状与挑战 在移动互联网时代&#xff0c;API作为应用与服务器通信的核心通道&#xff0c;其安全性直接关系到业务系统的稳定性和用户数据的安全。大麦APP作为国内领先的票务平台&#xff0c;面临着巨大的抢票压力和…...

标准、规范、规程有何区别与联系

标准、规范、规程有何区别与联系什么是标准&#xff1a;标准作为标准化的核心&#xff0c;其定义和解释也经历了一个较长的发展时期&#xff0c;最有影响的有三个&#xff1a;一是1934年盖拉德在其《工业标准化原理与应用》一书中对标准所作的定义&#xff0c;这也是世界上最早…...

项目管理实战:如何用关键路径算法优化你的开发周期(附Python代码示例)

项目管理实战&#xff1a;如何用关键路径算法优化你的开发周期&#xff08;附Python代码示例&#xff09; 在敏捷开发团队中&#xff0c;最常听到的抱怨莫过于"时间不够用"。上周我们的跨平台应用项目就遇到了典型困境&#xff1a;产品经理要求三周内完成支付模块重构…...

避雷针保护范围计算公式

避雷针保护范围计算公式 Rx=√H(2Hr-H)-√Hx(2Hr-Hx) Rd=√H(2Hr-H) 其中: Rx---避雷针在Hx高度平面上的保护半径M Hr---滚球半径M Hx---被保护物体高度M H---避雷针的计算高度M Rd---避雷针在地面上的保护半径M Rx=1.6Ha/(1+Hx/H) Rx---避雷针在Hx高度平面上的保护…...

石油干线管道关键参数稳定自动控制系统(CAP)研究

石油干线管道关键参数稳定自动控制系统(CAP)研究 摘要 石油干线管道是国家能源输送的重要基础设施,其运行过程中的压力、流量等关键参数的稳定控制直接关系到管道的安全性与经济性。本文针对石油干线管道参数控制的非线性、大滞后、强耦合等特点,设计并实现了一套关键参数…...

嵌入式蜂鸣器非阻塞管理库BuzzerManager深度解析

1. BuzzerManager 库深度解析&#xff1a;面向嵌入式系统的多路无阻塞蜂鸣器管理方案在嵌入式系统开发中&#xff0c;声音反馈是人机交互最基础、最可靠的物理通道之一。从工业设备的状态提示、医疗仪器的报警响应&#xff0c;到消费电子的按键确认、玩具的音效反馈&#xff0c…...

手把手教你用逻辑分析仪抓取并解析MIPI-CSI-2数据包(以RAW10格式为例)

手把手教你用逻辑分析仪抓取并解析MIPI-CSI-2数据包&#xff08;以RAW10格式为例&#xff09; 在嵌入式视觉系统的开发中&#xff0c;MIPI-CSI-2协议的数据流就像是一条暗河——虽然知道它的存在&#xff0c;但水面下的实际传输细节往往难以窥见。当摄像头输出的图像出现断层、…...

【NLP实战指南】FUNSD数据集:表单理解与结构化数据生成的挑战与机遇

1. FUNSD数据集&#xff1a;表单理解领域的"硬骨头" 第一次接触FUNSD数据集时&#xff0c;我被它满屏的噪点和五花八门的表单样式震惊了。这就像给你一堆被咖啡渍浸过的快递单、皱巴巴的申请表和模糊的扫描件&#xff0c;要求你准确提取所有信息。这个由199份真实扫描…...

Settingator:嵌入式参数管理库的轻量级设计与实践

1. Settingator 库概述&#xff1a;嵌入式设备与移动端配置协同的工程实践Settingator 是一个面向嵌入式系统的轻量级 Arduino 兼容库&#xff0c;其核心目标并非提供通用通信协议栈&#xff0c;而是构建一套可验证、可回滚、低侵入的运行时参数管理机制&#xff0c;专为配合同…...

linux学习进展 基础命令 vi基础命令

Linux系统的核心操作依赖命令行&#xff0c;掌握基础命令是入门Linux的关键&#xff0c;而vi编辑器作为Linux自带的文本编辑工具&#xff0c;日常使用频率极高。本次笔记主要记录Linux常用基础命令及vi编辑器的核心操作&#xff0c;方便后续复习巩固&#xff0c;兼顾实用性和易…...

21.4%高增速锁定!内容创作应用程序市场未来六年发展蓝图清晰,赛道潜力凸显

在数字化内容消费需求爆发式增长、生成式AI技术加速渗透的背景下&#xff0c;内容创作应用程序&#xff08;Content Creation Applications&#xff09;正从“工具型产品”向“智能创作生态平台”演进。据恒州诚思调研统计&#xff0c;2025年全球市场规模达126.5亿元&#xff0…...

OpenClaw新手避坑指南:Qwen3-14b_int4_awq模型对接5大误区

OpenClaw新手避坑指南&#xff1a;Qwen3-14b_int4_awq模型对接5大误区 1. 为什么写这篇文章 上周我在本地部署OpenClaw对接Qwen3-14b_int4_awq模型时&#xff0c;踩了无数坑。从baseUrl格式错误到上下文窗口超限&#xff0c;几乎把所有新手可能犯的错误都犯了一遍。最痛苦的是…...

三进制计算机:从数学理论到工程实践

1. 三进制计算机的数学基础1.1 进制效率的理论探讨在计算机科学领域&#xff0c;进制选择本质上是一个信息编码效率的问题。1948年&#xff0c;香农在他的开创性论文《通信的数学理论》中首次提出了信息熵的概念&#xff0c;这为我们理解不同进制的编码效率提供了理论基础。让我…...

9.7%年复合增长率!内容安全审查平台未来六年发展路径清晰,市场潜力凸显

在数字内容呈指数级增长、全球网络监管政策趋严的背景下&#xff0c;内容安全审查平台作为保障数字空间合规性的核心工具&#xff0c;正经历从“规则驱动”向“AI智能驱动”的范式转型。据恒州诚思调研统计&#xff0c;2025年全球市场规模达179.3亿元&#xff0c;预计至2032年将…...

ref vs reactive:Vue 3 响应式 API 到底该怎么选

在 Vue 3 的响应式系统中&#xff0c;ref 和 reactive 是最核心的 API&#xff0c;但它们的定位、使用场景和底层实现存在本质差异。理解二者的区别并合理选择&#xff0c;是掌握 Vue 3 响应式编程的关键。以下从 7 个维度深入剖析&#xff0c;提供 2000 字级别的详细指南。 1.…...

从 Options API 到 Composition API:你的 Vue 代码为什么需要重构?

从 Options API 到 Composition API&#xff1a;你的 Vue 代码为什么需要重构&#xff1f; 在 Vue.js 的发展历程中&#xff0c;Options API 曾是开发者构建组件的标准方式。但随着 Vue 3 的发布&#xff0c;Composition API 以其灵活性和可维护性优势逐渐成为主流选择。本文将…...

Vue 3 到底好在哪里?一文看懂 Composition API 的三大核心优势

Vue 3 到底好在哪里&#xff1f;一文看懂 Composition API 的三大核心优势 在前端框架的演进历程中&#xff0c;Vue 3 的发布堪称里程碑事件。其核心亮点之一——Composition API&#xff0c;彻底重构了组件逻辑的组织方式&#xff0c;解决了传统 Options API 在大型项目中的痛…...