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

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

首先到底要做一个什么东西?

我们要造一个C++ 高并发异步日志库,功能如下:

  1. LOG_INFO << "xxx"这种简单写法
  2. 自动带:时间、级别、文件名、函数名、行号
  3. 支持级别过滤(TRACE/DEBUG/INFO/WARN/ERROR/FATAL)
  4. 异步写文件,不卡主线程
  5. 文件自动滚动(按大小 / 按天)
  6. 线程安全,多线程一起写不乱

你平时写的:

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→TRACE,1→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 连接成功

组成部分:

  1. 时间戳 Timestamp
  2. 日志级别 INFO/DEBUG…
  3. 文件名(截取最后一段)
  4. 函数名__func__
  5. 行号__LINE__
  6. 用户输入的内容

核心函数:

// 构造函数:拼头部 LogMessage::LogMessage(level, file, func, line) { ss << 时间 << " " << 级别 << " " << 文件名 << " " << 函数 << " " << 行号; header_ = ss.str(); } // 重载 << 运算符,让你能流式写 template<class T> LogMessage& operator<<(const T& val) { text_ += val; return *this; } // 最终返回完整日志字符串 string LogMessage::toString() { return header_ + text_; }

第二阶段:核心大脑 Logger——日志器

作用:给用户提供最简单的接口,控制全局行为

它干 5件事:

  1. 全局日志级别控制——判断这条日志该不该输出
  2. 提供输出接口(控制台 / 文件 / 异步)——提供LOG_INFO等宏给你用
  3. 提供刷新接口——决定输出到哪(控制台 / 文件 / 网络)
  4. 线程安全控制
  5. 析构时输出日志

最关键的设计:利用析构函数输出日志

// 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 个函数:

  1. setLogLevel设置过滤级别,低于该级别直接丢弃。

  2. setOutput你可以指定输出位置:

    • 默认输出到控制台
    • 可以改成输出到文件
    • 可以改成输出到异步系统
  3. output真正执行输出的函数。

  4. 日志宏 LOG_XXX包装了文件、行号、函数名,让你用起来超级简单。

第三阶段:同步日志

流程超级简单:

LOG_INFO << "hello" → 生成 Logger 临时对象 → 构造 LogMessage,拼头部 → operator<< 写入内容 → 析构函数调用 output() → output 直接输出到控制台/文件

缺点:写磁盘是慢 IO!高并发下,业务线程会被日志卡住,性能暴跌。

所以我们必须做:异步日志

第四阶段:高性能核心 —— 异步日志 AsyncLogging

为什么要异步?

  • 同步:主线程自己写磁盘 →慢、阻塞
  • 异步:主线程只丢到内存 →极速、不阻塞后台线程慢慢写磁盘

异步日志设计思想:双缓冲 / 多缓冲技术

你可以理解为:两个缓冲区交换使用

  1. 前端缓冲区:主线程拼命往里写日志
  2. 后端缓冲区:后台线程拿去写入文件
  3. 当前端满了,直接交换两个缓冲区
  4. 后台线程默默写入,完全不影响主线程

这就是高并发日志库的核心!

类似于:两个桶接力倒水

  1. 前端桶:主线程疯狂写日志
  2. 后端桶:后台线程拿去写文件
  3. 前端满了 →交换两个桶
  4. 主线程继续写,后台线程慢慢刷盘

关键成员

  • currentBuffer_:当前写的缓冲区
  • buffers_:装满的缓冲区队列
  • mutex_:保护缓冲区
  • cond_:通知后台线程
  • thread:后台写线程

核心流程

  1. 用户 append加锁 → 往 currentBuffer 写 → 满了就放入队列 → 唤醒后端
  2. 后端线程 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() { vector<string> 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 个核心函数

  1. append()主线程调用,把日志塞缓冲区,超快!

  2. threadFunc()后台线程死循环:

    • 等待缓冲区满
    • 交换缓冲区
    • 写入文件
  3. start() / stop()启动 / 停止后台线程

第五阶段 :线程同步工具(CountDownLatch)

CountDownLatch.hpp——等线程启动好

作用:确保异步线程真正启动后,主线程才继续跑

用法:

  1. 初始化计数 1
  2. 主线程调用wait()
  3. 线程启动好调用countDown()
  4. 主线程继续执行

保证:日志线程没启动前,不写日志

第六阶段:文件写入模块——日志最终落地

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 件事:

  1. 生成文件名(包含时间、主机名、进程 ID)
  2. 判断是否要滚动文件
  3. 线程安全的写入接口

滚动规则

  1. 按大小滚动:默认 128KB,超过就切
  2. 按时间滚动:每天 0 点自动切新文件
  3. 每次写入都会检查:是否满了 / 是否跨天

核心函数

// 新建一个日志文件 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. 多级日志过滤:线上关闭调试日志
  2. 微秒级时间戳:高并发精准排序
  3. 自动携带上下文:文件、行号、函数
  4. 异步多缓冲:主线程无阻塞,高性能
  5. 日志文件滚动:按大小 / 时间切分
  6. 线程安全:多线程并发写不乱码

异步日志完整链路

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 日志库设计的,工业级标准,高并发稳定可靠。

http://www.jsqmd.com/news/604868/

相关文章:

  • DHT温湿度传感器高精度驱动库设计与实现
  • 2026年赣州智能边柜采购指南:五大实力厂家深度解析与选择策略 - 2026年企业推荐榜
  • DAB单级式双向AC-DC变换器软开关与功率因数校正协同优化策略
  • 高温袋技术演进与市场格局:2026年诚信厂家的价值锚点 - 2026年企业推荐榜
  • 决策参考:2026年江苏牛角椒种子核心供应商能力评估与选择建议 - 2026年企业推荐榜
  • 避坑指南:Ubuntu换源后apt update报错的5种修复方法(附清华源最新配置)
  • 专业之选:煜德智能设备有限公司,2026年高评价滚塑机供应商解析 - 2026年企业推荐榜
  • 别再到处找了!手把手教你用AWS CLI下载SpaceNet道路数据集(附加速技巧)
  • 2026江苏螺丝椒种子品牌综合评估报告:五大服务商实力全景解析 - 2026年企业推荐榜
  • 数据说话:2026年江西铝合金电缆桥架优选品牌实力拆解 - 2026年企业推荐榜
  • OpenClaw智能家居中枢:Qwen3-14b_int4_awq语音指令转API调用
  • 2024-2025不锈钢清洗剂选购全指南:五大品牌深度解析与采购建议 - 2026年企业推荐榜
  • 修复Transformer模型GUI界面摄像头黑白显示问题并将YouTo8模型训练数据迁移至Transformer模型
  • FDM vs IDM:两大下载神器对比评测,哪款更适合你的需求?
  • 2026年江苏观光小火车采购指南:五大实力厂商深度解析与选择策略 - 2026年企业推荐榜
  • 2026年温州企业GEO服务选型指南:五大顶尖服务商深度横评 - 2026年企业推荐榜
  • 避雷针保护范围计算公式
  • LangGraph 为什么成为 Multi-Agent 编排的事实标准
  • 前瞻2026:贵州重大项目建设空气型母线槽服务商综合评估与选购指南 - 2026年企业推荐榜
  • 2026全球资产配置服务市场深度解析:五家专业机构实力与特色全维度评估 - 2026年企业推荐榜
  • 避坑指南:Hive 3.1.3 在Linux上部署时,如何解决MySQL元数据中文乱码和日志Jar包冲突?
  • 2025届毕业生推荐的十大AI辅助写作方案解析与推荐
  • 零门槛掌握《经济研究》LaTeX模板:从排版小白到学术专家的蜕变指南
  • OpenClaw技能扩展实战:为Phi-3-mini-128k-instruct添加PDF处理能力
  • 项目管理实战:如何用关键路径算法优化你的开发周期(附Python代码示例)
  • 语雀文档本地化备份工具:轻量级工具实现全流程管理
  • 从ClickHouse迁移到StarRocks:我们团队踩过的坑和性能提升实战
  • AI立法者内战:机器人议员投票废除人类公民权
  • 2026河北碳化钨耐磨焊丝选型指南:洞悉趋势,精准匹配,赋能高效生产 - 2026年企业推荐榜
  • OpenClaw模型热切换:Qwen3-4B与其他LLM动态路由