ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Folly 日志处理器(Log Handler)完全指南:从内置 handler 到自定义实现

Folly 日志处理器(Log Handler)完全指南:从内置 handler 到自定义实现 Folly 日志处理器Log Handler完全指南从内置 handler 到自定义实现【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/follyfolly::logging是 Folly 库内置的高性能日志框架而LogHandler是其中负责接收并处理日志消息的核心抽象。本篇指南以 folly/logging/docs/LogHandlers.md 为骨架结合 Folly 仓库中的工厂与写入器源码系统讲解内置的stream/filehandler 类型、async/formatter等配置选项、默认 handler 的初始化行为以及如何通过LogHandlerFactory与StandardLogHandler实现完全自定义的日志处理器帮助你在实际项目中按需配置日志输出管道。LogHandler 接口日志处理的统一抽象在 Folly 的日志架构中LogHandler定义了希望被通知日志消息的类应遵循的接口。任何实现了该接口的对象都可以被挂载到特定的日志类别LogCategory上从而接收发送到该类别及其所有子类别children categories的日志消息。将LogHandler挂载到根类别root category上则该 handler 会收到每一条被启用的日志消息。从源码 folly/logging/LogHandler.h 可以看到该接口由三个纯虚函数构成任何子类都必须实现handleMessage(const LogMessage message, const LogCategory* handlerCategory)当一条日志消息被某个挂载了本 handler 的LogCategory处理时被调用。它始终在产生日志消息的线程中被调用源码注释明确指出 will always be invoked from the thread that logged the message因此 handler 可以利用这一保证来读取线程局部状态。注意handlerCategory是调用此方法的类别即 handler 挂载的类别可能与消息最初记录的类别不同——原始类别可以通过message-getCategory()获取。flush()阻塞直到所有已发送给该 handler 的消息都被处理完毕。对异步 handler 而言这保证已入队的消息已完成处理但flush()执行期间其他线程仍可能继续调用handleMessage()。getConfig()返回描述该 handler 当前配置的LogHandlerConfig对象。此外LogHandler自身带有独立的日志级别过滤能力每个 handler 可以设置自己的 level低于该 level 的消息会被直接丢弃。这允许同一个LogCategory挂载多个 handler 并做差异化过滤——例如本地文件 handler 记录全部消息而远程日志服务 handler 只处理ERROR及以上级别。LogHandler的默认 level 为LogLevel::NONE即默认处理所有消息相关说明同样见 LogHandler.h 的类注释。内置 Handler 之一stream类型在日志配置字符串中配置语法详见 folly/logging/docs/Config.md可以通过stream类型定义一个写入stdout或stderr的日志 handler。handler 的stream属性指定写入哪个标准流。例如下面这条配置定义了一个名为myhandler、写入 stderr 的 handlermyhandlerstream:streamstderr从实现上看streamhandler 由 folly/logging/StreamHandlerFactory.cpp 提供WriterFactory::processOption()处理stream选项其余选项如async、max_buffer_size委托给内部的FileWriterFactory处理createWriter()中若stream为空则抛出no stream name specified for stream handlerstream取值为stderr或stdout时分别以STDERR_FILENO/STDOUT_FILENO构造File对象ownsFd为false即不接管 fd 所有权其他值则抛出unknown stream ... : expected one of stdout or stderr异常。因此stream选项的合法取值只有stdout与stderr两个配置时必须精确匹配。内置 Handler 之二file类型与安全注意事项filehandler 类型用于把日志消息追加到磁盘文件。path选项控制写入的目标文件。例如myhandlerfile:path/var/log/my.logfolly/logging/FileHandlerFactory.cpp 中展示了它的底层行为WriterFactory::processOption()专门解析path选项其余选项同样委托给FileWriterFactorycreateWriter()在path_为空时抛出no path specified for file handler否则以O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC标志打开文件——即只写、追加、不存在则创建、同时设置 close-on-exec保证并发安全地往同一文件追加内容源码中还留有 TODO 注释T29811675说明未来可能支持日志轮转log rotation及相关控制参数当前版本尚无此能力。默认未注册仅在可信配置下启用需要特别强调的是filehandler默认不会被folly::initLogging()注册。原因是它允许根据配置字符串向任意文件追加内容属于潜在安全风险——如果你的程序以较高权限运行而低权限用户能够写入你的配置文件攻击者就可能通过配置让程序向任意路径追加日志内容。因此只有当你信任配置字符串的来源时才应启用这一 handler 类型。如需显式启用可在调用initLogging()之前注册FileHandlerFactory这样initLogging()的配置字符串中即可使用file类型folly::LoggerDB::get()-registerHandlerFactory( std::make_uniquefolly::FileHandlerFactory());registerHandlerFactory()的完整签名见 folly/logging/LoggerDB.hvoid registerHandlerFactory( std::unique_ptrLogHandlerFactory factory, bool replaceExisting false);第二个参数replaceExisting默认为false若该类型名已注册且replaceExisting为false会抛出std::range_error设为true则用新工厂替换旧工厂。与之对应的还有unregisterHandlerFactory(StringPiece type)可用于注销指定类型的工厂。Handler 选项详解内置 handler 类型支持若干选项来控制其行为。这些选项最终由StandardLogHandlerFactory::createHandler()统一处理见 folly/logging/StandardLogHandlerFactory.cpp格式化工厂与写入器工厂依次消费各自认识的选项未知选项会触发unknown option ...错误并汇总抛出。因此下面这些选项可自由组合在stream与filehandler 上。async同步写入还是异步写入async选项控制日志消息是在独立线程中异步写入asynctrue还是在产生消息的线程中立即写入asyncfalse。该选项由 folly/logging/FileWriterFactory.cpp 解析并决定创建哪类写入器asynctrue→ 创建AsyncFileWriterasyncfalse→ 创建ImmediateFileWriter。两种模式在日志生成速度超过写入速度时的行为截然不同asynctrue异步保证你的程序永远不会阻塞等待写日志。作为代价当缓冲被写满时 handler 会开始丢弃新的日志消息当它能够追上进度时会向输出中写入一条消息说明一共丢弃了多少条消息AsyncFileWriter中的getNumDiscardedMsg()负责生成这条提示见 folly/logging/AsyncFileWriter.h。asyncfalse同步保证不会丢弃任何日志消息代价是当消息生成速度超过写入速度时程序正常处理流程会被阻塞直到日志写完。崩溃安全性的权衡也与之直接相关asyncfalse能保证如果程序崩溃所有日志消息都已被刷出。例如某个线程记录一条日志后紧接着解引用空指针同步模式下该日志一定已落盘线程才会继续执行。asynctrue则可能丢失崩溃前刚刚产生的部分消息——因为日志 I/O 线程可能还没来得及把消息刷出产生消息的线程就已经崩溃了。这一设计取舍在 folly/logging/AsyncLogWriter.h 的类注释中也有明确陈述异步 I/O 的好处是日志写入永远不会拖慢或阻塞正常程序运行坏处是崩溃时可能丢失崩溃前瞬间产生的消息。max_buffer_size异步缓冲上限当asynctrue时max_buffer_size选项控制内存中可缓冲的日志数据量上限单位是字节。超过该上限的新日志消息将被丢弃。需要留意两条关键语义详见原文档与 AsyncLogWriter.h日志消息要么整条保留要么整条丢弃绝不保留半条消息该选项仅对异步 handler 有效在FileWriterFactory::createWriter()中若asyncfalse却指定了max_buffer_size会抛出the \max_buffer_size\ option is only valid for async file handlers值为0会被拒绝抛出must be a positive integer。AsyncLogWriter的默认缓冲上限为kDefaultMaxBufferSize 1024 * 1024即 1 MiB定义见 folly/logging/AsyncLogWriter.hsetMaxBufferSize()可在运行时动态调整。AsyncLogWriter本身是一个可扩展的抽象基类它在一个独立线程中执行日志 I/O子类只需重写performIO()即可自定义 I/O 行为消息管理与背压逻辑由基类自动完成。formatter日志格式化器formatter参数控制日志消息的格式化方式。当前内置的格式化器主要有两个在StandardLogHandlerFactory::createHandler()中根据formatter选项分派glog默认以类似 Google glog 的风格格式化日志消息。它额外支持log_thread_name布尔选项可取值true/false或1/0控制输出中是否包含线程名底层由GlogStyleFormatter实现见 folly/logging/GlogStyleFormatter.h。custom允许通过log_format选项指定自定义格式字符串并可通过colored选项always/auto/never默认never控制输出着色。coloredauto时会根据目标是否为 ttylogWriter-ttyOutput()自动决定是否着色底层由 folly/logging/CustomLogFormatter.h 实现。原文档指出未来可能增加更多内置 formatter同时也完全支持自行实现LogFormatter类详见下文自定义 Log Handler一节。补充level与sync_level尽管原文档未展开StandardLogHandlerFactory源码中还处理了两个值得了解的选项见 folly/logging/StandardLogHandlerFactory.cpplevel设置 handler 自身的过滤级别对应StandardLogHandler::setLevel()即低于该级别的消息被忽略的 handler 级过滤。sync_level对应StandardLogHandler构造函数中的syncLevel参数默认LogLevel::MAX_LEVEL。从 folly/logging/StandardLogHandler.h 可见handler 内部维护level_与syncLevel_两个原子级别用于控制消息同步/异步处理的临界点——这是高性能场景下精细调优的进阶选项。默认 Handler 配置默认情况下folly::initLogging()实现见 folly/logging/Init.cpp其最终调用LoggerDB::get().updateConfig(config)应用配置会创建一个名为default的单一日志 handler它被安装在根日志类别root category上所有日志消息都写入stderr使用类似 glog 的消息格式async选项默认关闭——这意味着initLogging()默认不会派生独立的日志 I/O 线程但当日志生成速度超过 stderr 写入速度时消息可能延迟程序的正常处理。对于希望避免日志造成性能抖动hiccups的高性能程序建议在defaulthandler 上启用async。这一改动通过日志配置字符串即可轻松完成。例如下面这条配置将根类别的日志级别设为WARN同时为defaulthandler 开启异步写入WARN; default:asynctrue配置字符串中default:asynctrue的语法即对名为default的 handler 设置async选项与定义新 handler如myhandlerstream:streamstderr的语法相对应。需要注意initLogging()对配置字符串的解析异常处理有initLogging()静默沿用默认配置与initLoggingOrDie()打印错误到 stderr 并exit(1)两种形式生产环境可直接使用后者。自定义 Log Handler如果内置 handler 无法满足需求Folly 日志框架允许完全自定义LogHandler。自定义的入口是LogHandlerFactoryAPILogHandlerFactory接口见 folly/logging/LogHandlerFactory.h由三个成员组成getType()返回该工厂类型的字符串名用于配置字符串中的类型名前缀且该名称在工厂生命周期内不可变createHandler(const Options options)依据选项字典std::unordered_mapstd::string, std::string创建 handlerupdateHandler()默认实现直接调用createHandler()新建对象子类可重写为原地更新既有 handler通过LoggerDB::get()-registerHandlerFactory()注册自定义工厂后parseLogConfig()解析出的配置字符串就能识别你的自定义 handler 类型——这与上文注册FileHandlerFactory是同一机制。StandardLogHandler两步式标准实现StandardLogHandler是LogHandler的一个开箱即用的实现见 folly/logging/StandardLogHandler.h它把日志消息处理拆分成两个可独立定制的步骤使用一个LogFormatter将LogMessage序列化为字符串使用一个LogWriter将格式化后的字符串写出。StandardLogHandler本质上是一个把可配置的LogFormatter与LogWriter串联起来的胶水类。这意味着如果你只想定制格式化或写出这两个步骤中的某一个就无需实现完整的LogHandler只需提供自定义的LogFormatter或LogWriterStandardLogHandlerFactory见 folly/logging/StandardLogHandlerFactory.h可用来实现你自己的LogHandlerFactory以自定义的 formatter 或 writer 类型创建StandardLogHandler。源码层面StandardLogHandler的formatter_、writer_、config_均为const成员构造后不可变这使得处理消息时无需加锁即可并发读取如需更改这些配置正确做法是创建新的StandardLogHandler并在LoggerDB中替换旧 handler。内置的stream与filehandler 正是这条扩展路径的典型产物——它们各自实现WriterFactory继承StandardLogHandlerFactory::WriterFactory并让StandardLogHandlerFactory完成 formatter 选择、选项校验与 handler 装配最终返回一个功能完整的StandardLogHandler。想深入了解各步骤职责划分可继续阅读 folly/logging/LogFormatter.h、folly/logging/LogWriter.h 以及日志配置解析的 folly/logging/LogConfigParser.h。小结Folly 日志框架以LogHandler接口为核心提供了清晰的扩展层次日常使用只需通过配置字符串选择streamstdout/stderr或file类型并按需组合async、max_buffer_size、formatter、level等选项追求低延迟时对defaulthandler 开启asynctrue并权衡崩溃丢消息的风险需要深度定制时则可沿LogHandlerFactory → StandardLogHandlerFactory → LogFormatter/LogWriter这条链路实现自己的处理管道。配置语法的完整细节类别名、级别、handler 定义与更新可参考 folly/logging/docs/Config.md类别与级别相关的说明见 folly/logging/docs/LogCategories.md 与 folly/logging/docs/LogLevels.md。【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表