Folly Logging 日志分类(Log Categories):层级模型、级别继承与消息向上传播原理详解
Folly Logging 日志分类Log Categories层级模型、级别继承与消息向上传播原理详解【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly本文以 Meta 开源的 C 日志库 folly logging 中的日志分类机制为核心深入讲解 LogCategory 的树形层级结构、级别level向下传播、日志消息向上冒泡这两大核心行为并结合 LogCategory.h 与 LogCategory.cpp 的源码实现与 LogCategoryTest.cpp 测试用例帮助你掌握如何通过分类名与inherit开关精细控制代码库各区域的日志详略程度以及如何利用根分类挂载全局日志处理器。读完本文你将能够熟练设计日志分类命名、利用XLOG()自动归类并通过配置文件精确调校任意模块的日志输出。什么是日志分类Log Category在 folly logging 中每一条日志消息都会被记录到一个具体的日志分类log category。一个日志分类由两部分配置组成级别设置level setting控制该分类下哪些日志消息应该被启用即满足什么级别门槛的消息才会被处理日志处理器列表log handlers控制被启用的日志消息要做什么——写到 stderr、写文件还是做其它处理。源码中LogCategory类正是存储某个日志分类的全部日志配置的载体见 LogCategory.h。该类与Logger分离设计多个Logger对象可以同时指向同一个LogCategoryLogger可视为一个指向LogCategory的轻量包装指针式对象。需要先建立的一个核心认知是级别过滤要不要记录由分类层级决定消息处理记录到哪里由分类上的处理器决定而过滤与处理是两套独立的传播机制——前者沿分类树向下传播见下文日志级别传播后者沿分类树向上冒泡见下文日志消息传播。日志分类层级Log Category Hierarchy树形结构根分类与父子关系日志分类以**树形层级hierarchy**组织除根分类root外每个分类都有一个父分类并可能拥有零个或多个子分类。分类层级由分类名决定分类名中的.字符充当分隔符。例如spacesim是spacesim.ships的父分类。根分类有两种写法.或空字符串。在源码中层级关系通过父指针和兄弟链表实现。LogCategory的私有成员parent_、firstChild_、nextSibling_把同一父分类下的子分类串成一个单向链表见 LogCategory.h新建分类的构造函数会自动把自己挂到父分类的firstChild_链表头部见 LogCategory.cpp。推荐命名跟随源码目录结构原文档推荐一种取名的实践让分类名跟随源码目录结构。例如一个太空模拟space simulator项目的日志分类层级局部视图如下. --- spacesim --- ships --- corvette -- cpp \ \ \- h | \- cruiser -- cpp | \- h | \- actors --- player -- cpp \ \- h \- ai --- enemy -- cpp \- h这种命名方式有两个直接好处天然获得层级控制力目录本身就是团队划分代码的方式把日志分类映射到目录就能以目录粒度统一调节某个团队/子系统的日志详略与XLOG()自动归类机制无缝衔接见下文无需开发者手工维护分类名。分类名规范化/、\与.等价一个值得注意的源码细节分类名并不只认.作为分隔符。LogName.cpp 中的canonicalize()把.、/、\三种字符统一视为分隔符并会折叠连续出现的多个分隔符、忽略首尾分隔符。也就是说..foo..、foo/bar与foo.bar会被规范化为同一个分类名。这一点被测试用例直接验证Logger foo{db, foo}与Logger foo2{db, ..foo..}指向同一个分类见 LogCategoryTest.cpp。LoggerDB::getCategory()也会先对名字做规范化处理。XLOG()自动选择分类XLOG()宏会根据源文件路径自动选择要使用的日志分类把路径中的目录分隔符替换为.即可得到分类名。例如src/tiefighter/thruster.cpp文件中默认的XLOG()分类为src.tiefighter.thruster.cpp详见 Usage.md 的Log Category Selection小节以及 xlog.h 中getXlogCategoryNameForFile()的实现。因此只要代码目录组织合理开发者几乎不需要手动操心分类名。如果希望某个.cpp文件中的所有XLOG()语句改用自定义分类名可以在该文件顶层作用域使用XLOG_SET_CATEGORY_NAME()宏覆盖默认分类注意该宏不应在头文件中使用否则会影响所有包含该头文件的.cpp文件详见 Usage.md 与 xlog.h。日志级别传播Log Level Propagation级别向下传播调父即调子日志级别设置会自动从某个分类向其所有子分类向下传播。如果在某个分类上提高了日志详略度即降低其最低启用级别默认情况下它的所有子分类都会继承这份提升。例如把spacesim.ships的级别设为INFO那么spacesim.ships自身以及spacesim.ships.corvette、spacesim.ships.fighter等子分类都会自动启用INFO及更高级别的日志消息。这使得一键控制整片代码区域的日志详略变得非常容易——这是该层级模型最核心的实用价值。源码实现有效级别effective level的逐级最小化级别向下传播在源码中体现为**有效级别effective level**的计算。LogCategory维护两个原子量level_本分类显式配置的级别其**最高位FLAG_INHERIT0x80000000**用于标记该分类是否继承父分类的有效级别见 LogCategory.heffectiveLevel_本分类及其所有父分类中的最低级别即真正用于日志门槛判断的值见 LogCategory.h。计算规则见 LogCategory.cppnewEffectiveLevel inherit ? std::min(level, parent_-getEffectiveLevel()) : level;即开启继承时有效级别 本分类级别与父分类有效级别的较小值越往上级别越低越是放宽关闭继承时有效级别就是本分类的显式级别。当某分类的有效级别发生变化时updateEffectiveLevel()会做两件事见 LogCategory.cpp同步XLOG缓存把所有注册到该分类的xlogLevels_供XLOG*()宏快速判断的std::atomicLogLevel指针更新为新的有效级别级联刷新所有子分类遍历firstChild_链表调用每个子分类的parentLevelUpdated()后者若开启继承则重新计算自身有效级别并继续向下级联见 LogCategory.cpp。这就是改一个父分类、整棵子树即时生效的底层机制。另外logCheck()用memory_order_relaxed加载有效级别做判断见 LogCategory.h让日志开关检查尽可能轻量——这也是 folly logging 宣称禁用状态下日志语句近乎零开销的关键之一。关闭继承inherit开关与:语法传播可以被按分类单独关闭把该分类的inherit设置关闭即可。例如关闭spacesim.ships.cruiser的inherit可以阻止它从父分类spacesim.ships乃至更上层spacesim、根分类继承提升的级别。这使得即使在所属的大分类打开了较高详略度也能单独压低特定分类的日志量。在配置层面关闭继承有两种等价方式基础配置语法用:代替例如folly.io:INFO会把folly.io的级别强制设为INFO且不从父分类继承即使父分类开了DBG2发到folly.io的DBG2消息仍会被丢弃见 Config.md 的Log Category Configuration一节JSON 配置语法为分类设置inherit: false字段默认缺省为true见 Config.md 的JSON Configuration Syntax一节。原文档还给出了一个非常实用的例子folly:WARN—— 把folly分类设为WARN并阻止其继承默认级别通常为INFO用于静默某个爱刷屏的组件同时保持其它一切不变。setLevel(LogLevel level, bool inherit true)的接口签名见 LogCategory.h印证了这一点inheritfalse正是想让子分类比父分类更不啰嗦时必需的手段。测试用例验证LogCategoryTest.cpp 的effectiveLevel测试完整覆盖了上述行为新建子分类foo.bar的自身级别为LogLevel::MAX_LEVEL未显式配置但有效级别等于默认级别db.setLevel(.foo, DBG0)后foo.bar的有效级别立即变为DBG0继承生效db.setLevel(., DBG0)、再把根设为ERR均验证了根级别变化沿树向下级联对foo.test.noinherit执行setLevel(CRITICAL, false)后其有效级别恒为CRITICAL即使根分类级别再低也不受影响。日志消息传播Log Message Propagation消息向上冒泡从叶子到根依次过处理器日志消息会沿分类层级向上传播一条消息先交给该消息所属分类上配置的所有LogHandler然后交给父分类的处理器一路向上直到根分类的处理器。例如一条记录到spacesim.ships.corvette.cpp的消息会依次被这些分类的处理器处理spacesim.ships.corvette.cpp → spacesim.ships.corvette → spacesim.ships → spacesim → 根分类(root)由此得出两个直接推论在根分类安装LogHandler会自动收到所有分类的日志消息——这是最常见的全局日志输出配置方式folly 默认的default处理器就是装在根分类上写 stderr详见 LogHandlers.md 的Default Handler Configuration一节在子分类安装LogHandler则只处理该分类及其子树的消息——适合做模块级定向输出。LogHandler收到的是完整的LogMessage对象如果需要在处理器内部再按级别或其它消息属性做二次过滤也是完全可以的LogHandlers.md 对处理器行为有更详细的说明。源码实现processMessageWalker 向上遍历消息传播在源码中由 LogCategory.cpp 的processMessageWalker()实现while (true) { category-processMessage(message); if (category-parent_ message.getLevel() category-propagateLevelMessagesToParent_.load( std::memory_order_relaxed)) { category category-parent_; } else { break; } }关键点propagateLevelMessagesToParent_默认值为LogLevel::MIN_LEVEL见 LogCategory.h即默认所有消息都继续向父分类传播该阈值可通过setPropagateLevelMessagesToParent()调高例如设为LogLevel::MAX_LEVEL表示任何消息都不再上传——典型场景是本分类及其子树已被重定向到其它输出目标不希望消息在父分类的处理器里重复出现见 LogCategory.h 与 LogCategory.h 的注释。在 JSON 配置中对应分类的propagate字段见 Config.md。processMessage()本身见 LogCategory.cpp会先拷贝处理器列表再逐个调用handleMessage()其中包含一个小型优化处理器数量不超过 5 个时使用栈上的std::array避免堆分配单个处理器抛异常时会通过LoggerDB::internalWarning()报告但继续把消息交给其余处理器不会中断整条传播链。FATAL 消息的特殊处理admitMessage()中还内置了致命消息的兜底逻辑见 LogCategory.cpp若消息级别是致命的FATAL/DFATAL在处理完传播后会先flushAllHandlers()确保消息落盘若此时发现一个处理器都没配置则直接把消息写到 stderr 以防程序静默崩溃即原文档在 LogLevels.md 中强调的FATAL不可禁用、无处理器时打印到 stderr 的行为随后调用std::abort()。实战用配置字符串操纵分类层级了解了上面两套传播机制后日常使用中你主要会通过配置字符串来操纵分类。基础配置语法为分号分隔的列表第一个分号之前是分类配置逗号分隔的分类级别列表其后是处理器配置见 Config.md 的Basic Configuration Syntax一节。以下是针对分类主题最常用的几个示例配置字符串含义WARN单独一个级别名 设置根分类级别为 WARNfollyINFO,folly.ioDBG2设置folly为 INFO、folly.io为 DBG2follyDBG2,folly.io:INFO同上但folly.io用:关闭继承DBG2 消息在该分类被丢弃folly:WARN把folly降为 WARN 且不继承默认级别静默刷屏组件ERROR:根分类级别设为 ERR并清空其处理器列表冒号后为空 清空完全不写冒号 保持原处理器不变配置既可用parseLogConfig()解析后通过LoggerDB::updateConfig()增量更新或LoggerDB::resetConfig()整体重置应用也可直接调用folly::initLogging()从命令行参数或配置文件初始化见 Usage.md 的Configuration一节及 Config.md 的Custom Configuration Mechanisms一节。JSON 配置语法还额外支持inherit与propagate两个分类字段适合更复杂的场景。小结分类 树形组织分类名以.或/、\分层根分类为./空串推荐让分类名跟随源码目录XLOG()自动按源文件路径归类级别向下传播改父分类的有效级别会自动级联到子树用:或inherit: false可对个别分类关闭继承消息向上传播一条消息会依次经过本分类、父分类、…、根分类上的所有处理器在根分类挂处理器即全局收日志子分类挂处理器即定向收日志两个传播方向互为补充级别传播管要不要记消息传播管记到哪里两者配合即可实现按目录一键调节日志详略、按分类定向分流输出的完整控制。若想进一步了解分类上挂载的处理器类型stream、file、async选项等可继续阅读 LogHandlers.md各类级别DBG0–DBG9、INFO、WARN、ERR、CRITICAL、DFATAL、FATAL等的语义见 LogLevels.md完整的配置语法与 JSON 格式见 Config.md。整体架构概览可参考 Overview.md。【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考