恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
fountain 日志模块 f_log 实战指南:LoggerFactory、Appender 与异步落盘原理
首页
资讯中心
/
fountain 日志模块 f_log 实战指南:LoggerFactory、Appender 与异步落盘原理
fountain 日志模块 f_log 实战指南:LoggerFactory、Appender 与异步落盘原理
发布时间:2026/10/15 23:16:56
后端Web框架【免费下载链接】fountain一个用于服务器应用开发的综合工具库。 - 零配置文件 - 环境变量和命令行参数配置 - 约定优于配置 - 深刻利用仓颉语言特性 - 只需要开发动态链接库fboot负责加载、初始化并运行 - 覆盖服务器应用开发的各种场景项目地址https://gitcode.com/Cangjie-SIG/fountain点击查看免费下载f_log 是 fountain 服务器开发工具库中的日志模块采用约定优于配置的设计思路只需通过环境变量声明 appender 即可完成日志输出目标的组装业务代码通过LoggerFactory按类型或名称获取Logger后直接记录日志写入动作默认异步执行。读完本文你将掌握 f_log 的环境变量配置体系、全部 7 类 appender 的字段与默认值、日志格式占位符、异步队列与超时策略以及文件轮转压缩等底层实现机制可直接在基于仓颉Cangjie语言的服务器应用中落地一套可观测的日志方案。一、模块定位与整体架构根据 f_log/README_en.md 的定义f_log 的职责是LoggerFactory按名称或类型取Logger日志通过 appender控制台、文件、轮转文件、Unix 域套接字、UDP、TCP 等输出记录动作默认异步执行。同时也可以通过fountain::fountain.log包使用本模块的同名 API对应聚合入口 src/fountain.cj 的导出体系。从源码结构看f_log 的调用链可以概括为业务代码 → LoggerFactory.getLogger(name/type) // 获取/缓存 LoggerWrapper → LoggerWrapper转发层支持运行时 refresh 热更新 → LoggerAppenderFacade异步门面继承 AsyncLogger → 广播给每个 AbstractLogger 实现 → Console / File / Unix / UnixDatagram / UDP / TCP / None关键实现文件分布在 f_log/src/base门面、异步、配置、模式解析与 f_log/src/impl各 appender 实现、f_log/src/output轮转文件与输出流LoggerFactory.cj用ConcurrentHashMapString, LoggerWrapper缓存日志实例getLogger(name)按字符串名称、getLoggerO()按泛型类型的全限定名取日志LoggerWrapper.cj持有AtomicReferenceLoggerAppenderFacade提供refresh()在运行期重建门面LoggerAppenderFacade.cj继承AsyncLogger把一条日志广播给名下所有 appenderLoggerConfig.cj读取全部logger_*配置并解析级别、pattern、路径、host/port 等字段LoggerAppenderCreator.cj按 kind 注册创建器在创建门面时汇聚所有 kind 产生的 appender。二、环境准备STDX 动态依赖f_log 依赖仓颉标准库的动态版本stdx使用前需要配置动态 stdx 库路径export CANGJIE_STDX_DYNAMIC_PATH/path/to/dynamic_stdx这与 f_log/cjpm.toml 中的编译配置对应包声明为output-type dynamic、compile-option --dy-std并在各目标平台的bin-dependencies中引用path-option [${CANGJIE_STDX_DYNAMIC_PATH}]。f_log 还依赖f_base、f_config、f_collection、f_data、f_io、f_time、f_util、f_version等 fountain 兄弟模块。三、配置体系一条环境变量声明一个 appenderf_log 是零配置文件模块所有配置都通过环境变量完成。配置键的统一形态是logger_appender_kindappender名1,appender名2,... # 声明某类 appender 有哪些实例 logger_appender_appender名_字段值 # 配置该 appender 的字段其中kind是 appender 类型关键字如console、file、udpappender名是任意合法的标识符如FDemoConsole。这种两层结构由 LoggerConfig.cj 实现getAppenders(kind)读取logger_appender_kind并做逗号切分getString(appender, item)读取logger_appender_appender_item所有取值都带默认值兜底。3.1 完整配置示例README 给出的完整示例注意其中以#注释的项均为可选# export cjHeapSize4GB # pattern 可省略有默认值 # %level 记录当前日志级别 # %name 记录当前日志名称 # %app 记录应用名f_version.AppVersion.name # %appver 记录应用版本号f_version.AppVersion.version # %d 记录当前日志时间花括号内是时间格式 # %m 记录当前日志消息文本 # %tid 记录当前线程ID # %pid 记录当前进程ID # export loggerAsyncBufsize2 # 异步日志缓存池的初始化大小默认是1024 export logger_appender_consoleFDemoConsole # 控制台日志 appender 名可任意起名需符合标识符规范 export logger_appender_FDemoConsole_levelDEBUG # 控制台日志级别 export logger_appender_FDemoConsole_pattern[%level-%name]%d{yyyy/MM/dd,HH:mm:ss.SSS}|%tid;%m # 控制台日志格式可不指定用默认值 export logger_appender_fileFDemoFile # 文件日志 appender 名可任意起名 export logger_appender_FDemoFile_levelINFO # 文件日志级别 export logger_appender_FDemoFile_pattern[%level-%name]%d{yyyy/MM/dd,HH:mm:ss.SSS}|%tid;%m # 文件日志格式此为默认值 export logger_appender_FDemoFile_path./log/fdemo.log # 日志文件路径 export logger_appender_FDemoFile_rotateDurationDAY # 按自然天切割日志 # export logger_appender_FDemoFile_rotateSize100000000 # 按日志文件大小切割文件字节数达到该值将重命名并创建新文件3.2 日志格式占位符pattern支持以下占位符解析逻辑位于 LogPattern.cj 的compile方法编译为一系列LogPart后顺序渲染占位符含义来源%level当前日志级别LogLevel.text见 LogLevel.cj%name当前日志名称Logger 的 name%app应用名f_version.AppVersion.name%appver应用版本号f_version.AppVersion.version%d{格式}当前日志时间花括号内为时间格式DateTime.format(format)无花括号时默认yyyy-MM-dd,HH:mm:ss.SSS%m当前日志消息文本message 的toString()%tid当前线程 IDThread.currentThread.id%pid当前进程 IDenv.getProcessId()此外compile还额外支持%fverfountain 版本号FountainVersion与%tname当前线程名。默认 pattern 为[%level-%name] %d{yyyy/MM/dd,HH:mm:ss.SSS}|%m见 LogPattern.cj 中LogPattern.default。3.3 全局与异步相关配置项除 appender 声明外还有一组全局配置loggerAsyncBufsize异步日志缓冲池的初始化大小默认1024LoggerConfig.cj 的getAsyncBufSize()logger_asyncWaitTimeout等待异步队列的时间默认Duration.millisecond * 5getAsyncWaitTimeout()同时loggerAsyncTimeout控制入队等待、loggerAsyncTimeoutPolicy控制超时策略见下文异步机制一节LoggerConfig.filtermut prop可设置一个LogFilter用于日志数据过滤/脱敏。四、代码使用按类型或按名称取 Loggerf_log 的使用非常简洁导入fountain::f_log.*后即可获取 Loggerimport fountain::f_log.* private static let log1 LoggerFactory.getLoggerTypeName() private static let log2 LoggerFactory.getLogger(LoggerName) public func foo(name: String): Unit { try{ log1.info{log message: ${name}} log1.info(log message) log1.debug{log message: ${name}} log1.debug(log message) log1.warn{log message: ${name}} log1.warn(log message) }catch(e: Exception){ log1.error(e){log message: ${name}} log1.error(log message, e) log1.warn(e){log message: ${name}} log1.warn(log message, e) log1.info(e){log message: ${name}} log1.info(log message, e) log1.debug(e){log message: ${name}} log1.debug(log message, e) } }从 LoggerFactory.cj 可以看到三种重载getLogger(name: String)、getLoggerO()把泛型O的全限定名作为日志实例唯一标识内部通过TypeInfo.ofO()取得、getLogger(typeInfo: TypeInfo)。同一名称的日志实例会被ConcurrentHashMap缓存复用若在不同代码处以相同名字调用getLogger(name, attrs)并携带attrs请务必保证attrs一致否则只保留最后一次调用的attrs。在 Logger.cj 中Logger继承stdx.log.Logger提供了完整的级别方法族每个级别都支持多种调用形态普通字符串log.info(log message)惰性闭包避免昂贵字符串构造log.info{log message: ${name}}携带异常log.error(log message, e)、log.error(e){log message: ${name}}、log.error(e)模板参数对象、Array、ArrayList、HashMap、TreeMap、ConcurrentHashMap、LinkedHashMaplog.info(msg {field}, obj)底层通过f_util.TextTemplate编译{...}占位并格式化级别覆盖FATAL、ERROR、WARN、INFO、DEBUG、TRACE。在 AbstractLogger.cj 中写日志时会先取DateTime.now()产生日志的时间比记录时间更重要异步队列可能使落盘顺序与产生顺序不一致与Thread.currentThread.id再做级别判断、渲染与写出。五、Appender 类型一览与专属字段logger_appender_kind是以逗号分隔的 appender 名列表kind与参数类、输出目标、专有配置字段对应关系如下源自 f_log/README_en.mdkind参数类输出目标专有配置字段consoleConsoleLoggerParams标准输出pattern、level、closablefileFileLoggerParams基类RotatableFileParams文件path默认工作目录/logs/命令名.log、rotateDuration默认DAY、rotateSize默认Int64.Max、compressFormat、url参数类还公开fileSize/timeunit/compress字段unixUnixLoggerParamsUnix 域套接字host/portunixDatagramUnixDatagramLoggerParamsUnix 域数据报host/portudpUdpLoggerParamsUDPhost、porttcpTcpLoggerParamsTCPhost、port程序内NoneLogAppender丢弃closable各类 appender 在模块加载时通过register()把创建器注册进LoggerAppenderCreator见 LoggerAppenderCreator.cj。各实现源文件为控制台ConsoleLoggerAppender.cj输出到env.getStdOut()且closable恒为false文件FileLoggerAppender.cj输出为RotatableFileUnix 域套接字UnixLoggerAppender.cj默认路径/tmp/log/unix.log支持writeTimeout、bufSize字段Unix 域数据报UnixDatagramLoggerAppender.cj默认路径/tmp/log/unixDatagram.log支持sendTimeout、bufSizeUDPUdpLoggerAppender.cj默认127.0.0.1:65534支持sendTimeoutTCPTcpLoggerAppender.cj默认127.0.0.1:65535支持bufSize发送缓冲区开启 keepAlive丢弃NoneLogAppender.cj级别恒为LogLevel.OFFclosable恒为false。值得注意的细节可从对应源码确认udp/tcp的host字段支持host:port内联写法——当不单独配置port而host中包含冒号时实现会从host中拆分出端口见 UdpLoggerAppender.cj 与 TcpLoggerAppender.cj 的newParams。unix/unixDatagram在套接字关闭后会按记录的path自动重建各实现append中的重建逻辑。另外BaseLoggerParams.registerLoggerParams.cj是所有参数类的公共注册入口它会按kind读取 appender 名列表为每个 appender 构建参数对象并复用相同参数appenderMap按参数哈希命中的已建 appender 实例当配置变化导致某组参数不再被引用时会把旧 appender 标记closable true后移出复用表。六、文件轮转与压缩RotatableFilefile类 appender 的输出对象是RotatableFileRotatableFile.cj它同时支持按时间与按大小两种切割策略二者可叠加rotateDuration时间单位DAY/HOUR/WEEK/MINUTE/MONTH/YEAR/SECOND等通过f_time.TimeUnit.tryParse解析默认DAY即按自然天切割内部用Timer.after在下一个时间边界触发轮转rotateSize文件字节阈值默认Int64.Max每次write前都会比较当前文件大小与累计写入量超过阈值即轮转。rotateSize支持100000000这样的裸数字也支持100MB、1GB这类带单位写法computeBytes支持 b/k/m/g/t/p/e 进制见 RotatableFile.cj 的computeBytescompressFormat轮转后旧文件的压缩格式LogFileCompressFormat枚举含NonCompression、Deflate(CompressLevel)、GZip(CompressLevel)级别可为BestSpeed/DefaultCompression/BestCompression压缩在独立队列线程中异步执行Deflate 产物后缀.lz、GZip 产物后缀.gz。轮转动作的本质是关闭当前文件句柄 →rename(originPath, to: originPath 时间戳)→ 异步压缩旧文件 → 重新以Append模式打开原路径新文件。时间后缀由genTime按时间单位生成如DAY生成yyyyMMdd。文件所在目录不存在时会被递归创建。url字段为fileappender 提供了一种 URL 形态的配置入口格式如file://path?rotateSize...rotateDuration...compressFormat...其查询参数会覆盖RotatableFileParams中的对应字段见RotatableFile的init(url:params:)构造。RotatableFile的轮转逻辑有对应测试 RotatableFile_test.cj测试中以SECOND为单位、fileSize 0触发高频轮转并配置 GZip 压缩。七、异步机制队列、缓冲区池与超时策略日志写入默认走异步队列链路核心在 AsyncLogger.cj业务线程调用log.info{...}时AbstractLogger只采集调用点的时间戳与线程 ID然后把广播给所有 appender的闭包投入ArrayBlockingQueue每个LoggerAppenderFacade都有一个以f_log.LoggerAppenderFacade_name为 tag 的队列队列容量即loggerAsyncBufsize默认 1024后台消费线程spawn后循环queue.remove()()真正执行级别判断、pattern 渲染与落盘。队列满时的行为由loggerAsyncTimeout入队等待时长默认Duration.Max即队列满时会一直等待而不是丢日志与loggerAsyncTimeoutPolicy决定策略枚举AsyncTimeoutPolicyAsyncTimeoutPolicy.cj支持三种取值策略行为AlwaysWaiting一直等待直到入队成功死等Discard直接丢弃本条日志默认值Abort抛出LogException(to add for current logger is timeout ...)写入侧使用池化的SyncQueueOutputStream字节缓冲区从静态池借用池大小同样由loggerAsyncBufsize决定进程级静态单例只在首次使用时按当时配置建立借不到缓冲区时会等待logger_asyncWaitTimeout默认 5ms超时则打印AsyncLogger.SyncQueueOutputStream.EmptyPool并丢弃本次写入。因此loggerAsyncBufsize同时影响异步队列容量与缓冲区池大小缓冲区池是进程级静态单例改这个配置后只有重启进程才能生效队列满默认是死等loggerAsyncTimeout默认Duration.Max想主动丢日志需显式配置 timeout 加 policy。八、运行期热更新Config.refresher 与 LoggerFactory.refreshf_log 支持运行期刷新配置。LoggerConfig的static init()会调用Config.refresher(confPrefix, LoggerFactory.refresh)LoggerConfig.cj把LoggerFactory.refresh注册为logger前缀配置的刷新回调刷新时 LoggerFactory.cj 遍历所有LoggerWrapper调用其refresh()LoggerWrapper.cj后者在锁内用LoggerConfig.createFacade(name)重建门面并compareAndSwap原子替换旧门面最后关闭旧门面。由代码结构可以推断appender 的名字、级别、pattern、路径、切割与压缩配置都能在刷新后生效而loggerAsyncTimeout/loggerAsyncTimeoutPolicy/异步队列容量在门面构造时读取刷新同样生效只有loggerAsyncBufsize的缓冲区池大小那一半进程级静态单例只能在重启后生效。九、LogFilter结构化数据过滤LoggerConfig.filter是mut prop可设置自定义LogFilterLogFilter.cj。LogFilter接口提供两个默认透传的过滤方法filter(attr: Attr): ?Attr与filter(name: String, value: String): ?(String, String)。当使用带结构化参数的日志 API对象或HashMap等时Logger.appendTLogger.cj会逐字段调用filter(name, value)返回None即表示该字段被过滤可用于脱敏等场景。AbstractLogger.log(record)在写出attrs时同样会经过LoggerConfig.filter.filter(attr)过滤。十、注意事项与最佳实践结合 README 的 Notes 一节与源码实现实践中有几点值得留意异步队列不阻塞业务线程日志写入走异步队列LoggerAppenderFacade : AsyncLogger队列满时按loggerAsyncTimeoutPolicy丢弃而不是阻塞业务线程loggerAsyncBufsize只影响缓冲池初始化大小。级别可运行期调整Logger.level是mut prop可在运行期动态调级traceEnabled/debugEnabled/infoEnabled/warnEnabled/errorEnabled/fatalEnabled属性用于在业务代码中预先判断避免构造昂贵的日志参数虽然框架在写日志前已做过级别判断见 Logger.cj 类注释。%app/%appver来自f_version.AppVersion应用未调用AppVersion.set(banner, name, version)时AppVersion.cj这两个占位符输出为空字符串。同名 Logger 的 attrs 一致性不同位置用相同名字取 Logger 并携带attrs时需保证 attrs 一致否则只保留最后一次调用的 attrs。进程退出自动关闭LoggerWrapper构造时通过env.atExit注册关闭回调进程退出时会关闭门面并排空队列AsyncLogger.doClose会等待队列清空并休眠一小段让写线程写出最后一个字节数组。全局 Logger模块初始化时通过setGlobalLogger(LoggerFactory.getLogger(fountain::f_log.global))设置了全局日志业务代码可通过stdx.log.getGlobalLogger获取见 global.cj。测试参考LoggerAppenderCreator.register(test1, ...)展示了自定义 appender 的注册方式logger_test.cj 中有getLogger(LoggerTest)的用法示例。通过环境变量与代码 API 的组合f_log 让服务器应用可以在不写任何配置文件的前提下快速搭建控制台 轮转文件 远程日志采集TCP/UDP/Unix 域套接字的完整日志链路并在高并发下借助异步队列与丢弃策略保护业务线程。赞分享后端Web框架【免费下载链接】fountain一个用于服务器应用开发的综合工具库。 - 零配置文件 - 环境变量和命令行参数配置 - 约定优于配置 - 深刻利用仓颉语言特性 - 只需要开发动态链接库fboot负责加载、初始化并运行 - 覆盖服务器应用开发的各种场景项目地址https://gitcode.com/Cangjie-SIG/fountain点击查看免费下载相关推荐fountain 日志模块 f_log 实现思想与实战LogPattern 模板、异步 Appender 与零配置启动fountain 日志模块 f_log 实现思想与实战LogPattern 模板、异步 Appender 与零配置启动 本文以 fountain 开源仓库的《后端Web框架fountain f_log 日志模块深度解析零配置文件、异步队列与多 Appender 实战fountain f_log 日志模块深度解析零配置文件、异步队列与多 Appender 实战 fountain 的 f_log 模块实现了零配置文件理念后端Web框架fountain 框架 f_log 日志模块全解析从环境变量配置到异步输出与 Appender 扩展fountain 框架 f_log 日志模块全解析从环境变量配置到异步输出与 Appender 扩展 导读 f_log 是 fountain 框架中负责日志记后端Web框架创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考