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

【C++】深入解析日志框架调用链

文章目录

      • 一、完整函数调用链
        • 场景1:全局根日志器输出DEBUG日志(最常用场景)
        • 场景2:创建异步日志器并输出INFO日志
        • 场景3:滚动文件Sink输出日志(RollSink)
        • 场景4:缓冲区自动扩容(Buffer)
      • 二、关键工具函数调用链(util.hpp)
      • 三、核心依赖调用链(辅助场景)
      • 总结

一、完整函数调用链

覆盖日志框架的主要使用场景(同步日志输出、异步日志输出、日志器创建、日志格式化、文件Sink输出等)。

场景1:全局根日志器输出DEBUG日志(最常用场景)
// 1. 宏调用(bitlog.h) LOGD("debug msg: %s", "test") └── LOG_DEBUG(ApeLog::rootLogger(), fmt, ##__VA_ARGS__) └── (logger)->debug(fmt, ##__VA_ARGS__) └── Logger::debug(const char* file, size_t line, const char* fmt, ...) // logger.hpp ├── Logger::shouldLog(LogLevel::value::DEBUG) // 检查日志级别 ├── va_start/va_end(初始化可变参数) └── Logger::log(LogLevel::value::DEBUG, file, line, fmt, al) // 核心日志处理 ├── vasprintf(格式化可变参数为字符串) ├── LogMsg构造函数(message.hpp) │ └── util::date::now() // 获取时间戳(util.hpp) │ └── std::this_thread::get_id() // 获取线程ID ├── Formatter::format(ss, log_msg) // 格式化日志(formatter.hpp) │ └── 遍历Formatter::_items,依次调用FormatItem::format │ ├── TimeFormatItem::format → localtime_r + strftime │ ├── ThreadFormatItem::format → 输出线程ID │ ├── LevelFormatItem::format → LogLevel::toString │ ├── NameFormatItem::format → 输出日志器名称 │ ├── CFileFormatItem::format → 输出文件名 │ ├── CLineFormatItem::format → 输出行号 │ ├── MsgFormatItem::format → 输出日志消息 │ └── NLineFormatItem::format → 输出换行 └── Logger::logIt(ss.str()) // 纯虚函数,由子类实现 └── SyncLogger::logIt(const std::string& msg) // 同步日志器输出 ├── std::unique_lock<std::mutex>(加锁) └── 遍历Logger::_sinks,调用LogSink::log └── StdoutSink::log(const char* data, size_t len) // 控制台输出(sink.hpp) └── std::cout.write(data, len)
场景2:创建异步日志器并输出INFO日志
// 1. 构建异步日志器(logger.hpp) GlobalLoggerBuilder builder; builder.buildLoggerName("async_logger"); builder.buildLoggerType(Logger::Type::LOGGER_ASYNC); builder.buildFormatter("%d{%Y-%m-%d} %t [%p] %m%n"); builder.buildSink<FileSink>("./logs/async.log"); Logger::ptr logger = builder.build(); ├── GlobalLoggerBuilder::build() // 构建并注册日志器 ├── 检查日志器名称非空 ├── 检查格式化器(无则创建默认Formatter) │ └── Formatter::Formatter(pattern) │ └── Formatter::parsePattern() // 解析格式字符串 │ └── Formatter::createItem → 创建对应FormatItem实例 ├── 检查Sink(无则添加StdoutSink) ├── 创建AsyncLogger实例 │ └── AsyncLogger::AsyncLogger(...) │ └── Logger::Logger(...) // 父类构造 │ └── _looper = std::make_shared<AsyncLooper>(回调函数) // looper.hpp │ └── AsyncLooper::AsyncLooper(cb) │ └── _thread = std::thread(&AsyncLooper::worker_loop, this) // 启动工作线程 └── loggerManager::getInstance().addLogger(...) // 注册到管理器 // 2. 输出INFO日志 LOG_INFO(logger, "info msg: %d", 123) └── Logger::info(...) ├── Logger::shouldLog(LogLevel::value::INFO) ├── Logger::log(...) // 同场景1,格式化日志 └── Logger::logIt(...) └── AsyncLogger::logIt(const std::string& msg) // 异步日志器输出 └── _looper->push(msg) // 推送消息到异步缓冲区 ├── AsyncLooper::push(const std::string& msg) // looper.hpp │ ├── std::unique_lock<std::mutex>(加锁) │ ├── _push_cond.wait(等待缓冲区有空间) │ ├── _tasks_push.push(msg.c_str(), msg.size()) // 写入缓冲区 │ └── _pop_cond.notify_all()(唤醒工作线程) └── 工作线程执行AsyncLooper::worker_loop() ├── _pop_cond.wait(等待有消息) ├── _tasks_push.swap(_tasks_pop)(交换缓冲区) ├── 调用回调函数(AsyncLogger::backendLogIt) │ └── 遍历_sinks,调用LogSink::log │ └── FileSink::log(const char* data, size_t len) // sink.hpp │ ├── FileSink::initLogFile()(文件未打开则初始化) │ │ ├── util::file::path(filename) // 获取文件目录(util.hpp) │ │ └── util::file::create_directory(path) // 创建目录 │ │ ├── util::file::exists(path) // 检查目录是否存在 │ │ └── mkdir(系统调用创建目录) │ └── _ofs.write(data, len) // 写入文件 └── _tasks_pop.reset()(重置缓冲区)
场景3:滚动文件Sink输出日志(RollSink)
logger->warn("warn msg") └── Logger::warn(...) ├── Logger::log(...) └── Logger::logIt(...) └── SyncLogger::logIt(...) └── RollSink::log(const char* data, size_t len) // sink.hpp ├── RollSink::initLogFile() // 检查是否需要滚动文件 │ ├── 判断文件大小是否超过_max_fsize │ ├── RollSink::createFilename() // 创建带时间戳的文件名 │ │ ├── time(NULL) + localtime_r(获取本地时间) │ │ └── std::stringstream拼接文件名 │ ├── util::file::create_directory(util::file::path(_basename)) // 创建目录 │ └── _ofs.open(name, 二进制+追加模式) ├── _ofs.write(data, len) // 写入文件 └── _cur_fsize += len // 更新当前文件大小
场景4:缓冲区自动扩容(Buffer)
AsyncLooper::push(msg) → _tasks_push.push(msg.c_str(), msg.size()) └── Buffer::push(const char* data, size_t len) // buffer.hpp ├── Buffer::ensureEnoughSpace(len) // 检查并扩容 │ ├── 判断len > writeAbleSize() │ ├── 计算new_capacity(分阈值扩容:<10MB翻倍,≥10MB+1MB) │ └── _v.resize(new_capacity) // vector扩容 ├── std::copy(data, data+len, &_v[_writer_idx]) // 拷贝数据 └── _writer_idx += len // 移动写指针

二、关键工具函数调用链(util.hpp)

// 1. 创建多级目录 util::file::create_directory(path) ├── util::file::exists(path) // 检查路径是否存在 │ └── stat(path.c_str(), &st) // 系统调用 ├── 遍历路径,逐段解析目录 ├── util::file::exists(subdir) // 检查子目录是否存在 └── mkdir(subdir.c_str(), 0755) // 系统调用创建目录 // 2. 获取文件所在目录 util::file::path(name) ├── name.find_last_of("/\\") // 查找最后一个分隔符 └── name.substr(0, pos+1) // 截取目录部分 // 3. 获取当前时间戳 util::date::now() └── (size_t)time(nullptr) // 系统调用

三、核心依赖调用链(辅助场景)

// 1. 日志器管理器(单例) loggerManager::getInstance() ├── static loggerManager log_mgr; // 单例初始化 ├── loggerManager::loggerManager() // 私有构造 ├── LocalLoggerBuilder::build() // 创建根日志器 └── _loggers.insert("root", _root_logger) // 注册根日志器 // 2. Sink工厂创建实例 SinkFactory::create<FileSink>(filename) // sink.hpp └── std::make_shared<FileSink>(filename) └── FileSink::FileSink(filename) ├── util::file::path(filename) ├── util::file::create_directory(path) └── _ofs.open(filename, 二进制+追加模式)

总结

  1. 核心流程:日志宏调用 → 日志级别检查 → 日志格式化 → 日志输出(同步/异步)→ Sink写入(控制台/文件/滚动文件),其中异步日志通过AsyncLooper的生产者-消费者模型实现。
  2. 关键依赖util.hpp提供的文件/时间工具是日志框架的基础(目录创建、时间戳、文件路径解析),Buffer类为异步日志提供高效的内存管理,Formatter类通过组合模式实现灵活的日志格式解析。
  3. 线程安全:同步日志通过std::mutex保证输出安全,异步日志通过条件变量+互斥锁实现缓冲区的线程安全操作,滚动文件Sink通过文件流追加模式保证多线程写入不冲突。
http://www.jsqmd.com/news/492685/

相关文章:

  • 2026年03月16日全球AI前沿动态
  • SUNFLOWER MATCH LAB在STM32嵌入式设备上的轻量化部署实践
  • Phi-3-mini-128k-instruct多轮对话连贯性展示:技术方案讨论实录
  • Qwen3-14B-INT4-AWQ快速部署SpringBoot微服务项目框架
  • OpenClaw(龙虾)秒级部署指南及安全避坑手册
  • Dify向量检索精度翻倍的关键:不是换模型,而是重排序!3类Rerank算法在真实业务场景中的A/B测试数据全公开
  • 智能排障:结合快马多模型ai,为openclaw本地部署难题提供实时解决方案
  • 衡山派开发板红外编解码模块驱动移植与NEC协议应用实战
  • 立创EDA开源项目:LED-编码器交互模块设计与8种显示模式详解
  • 批量逆地理编码实战:从Excel坐标到结构化地址(附完整代码)
  • Qwen-Ranker Pro入门必看:如何评估重排序效果——NDCG@5指标计算示例
  • 从均匀分布到参数估计:极大似然法实战解析
  • Java-语法基础1-[与C语言的异同]
  • Phi-3-vision-128k-instruct可部署方案:单卡3090/4090高效运行128K视觉模型
  • Navicat数据同步实战:从单向合并到双向协同
  • 实测分享:Ollama部署translategemma-27b-it图文翻译模型,效果惊艳
  • B003 找循环节 建图 ABC167D
  • CAN总线滤波秘籍:SJA1000的验收滤波器配置全解析(BasicCAN vs PeliCAN模式)
  • 短链接生成器架构解密:62 进制编码 + 分布式 ID,如何让 6 位字符支撑 568 亿个网址?
  • JetBrains IDE试用期管理工具:从痛点到解决方案的完整指南
  • Ollama部署Llama-3.2-3B避坑指南:常见问题与解决方案
  • 都在用 OpenClaw 跑 Skill,但你写的“技能”为什么总让 AI 频繁罢工?
  • uni.createInnerAudioContext音频播放全攻略:从基础使用到duration获取异常处理
  • 简单研究一下 shipfast 的收益排行榜上的 SaaS 网站都是干什么的(转)
  • 实时口罩检测-通用应用指南:智能考勤与公共卫生管理解决方案
  • 开箱即用:Hunyuan-MT 7B翻译镜像,原文输入→一键翻译→实时展示
  • 关于 Amazon Linux 2023 (AL2023) 默认情况下确实没有 /var/log/secure 文件的解决方法
  • Vivado 2024.2编译提速秘籍:实测32线程设置与16线程性能天花板
  • Spring AI + RAG 构建电商智能客服:从 PDF 文档解析到精准问答的全链路实战
  • gte-base-zh效果对比图谱:t-SNE+UMAP双视角展示中文语义空间结构