3. 常见日志框架介绍

日志框架这东西,说简单也简单,说复杂还真能水好几章。我最早接触的是 Log4j 1.x,那时候觉得能写日志就不错了。后来接手过一个老项目,Log4j 1.x 版本居然还在用,结果某天安全扫描报了个高危漏洞——CVE-2019-17571,搞得我们连夜升级。从那以后,我养成了一个习惯:选日志框架时,先看看它背后有没有活跃的社区和定期的安全更新。

这节课咱们不扯太高大上的理论,我直接给你盘点一下 Java 生态和 Python 生态里最常见的几个日志框架。你想想看,一个系统如果日志输出都没管好,线上出问题你连查都查不了,那多被动。

3.1 日志门面 vs 日志实现

先理清一个概念。很多新手会把「日志框架」混为一谈,其实它们分两类:门面(Facade)和实现(Implementation)。门面只定义接口,不干活;实现才真正去写文件、发网络、控制格式。我建议你记住这张表:

角色 例 说明
门面 SLF4J、Commons Logging、Python logging.getLogger 只提供 API,不涉及实际输出
实现 Logback、Log4j2、Log4j1、Java Util Logging 负责格式化、写入目的地

为什么要拆开?说白了就是为了解耦。你代码里只用 SLF4J,底层换成 Logback 还是 Log4j2,一行配置改掉就行,代码不用动。

3.2 Java 四大天王

3.2.1 Log4j 1.x —— 老前辈

Log4j 1.x 可以说是日志框架的鼻祖,但现在已经寿终正寝了。Apache 官方在 2015 年就宣布它 EOL(End of Life),不再维护。我记得有一次项目里还用的 Log4j 1.2.17,硬生生因为一个 NDC 线程泄漏问题排查了三天。嗯,这里要注意:新项目千万不要再用 Log4j 1.x,它连异步日志都不支持,性能也差一大截。

3.2.2 Log4j 2.x —— 性能怪兽

Log4j 2 跟 1.x 基本没血缘关系,完全是重写的。它的异步日志用的是 Disruptor 环形队列,吞吐量能比 Logback 高出一个量级。你想想看,如果系统每秒要记几十万条日志,Log4j 2 基本是首选。

配置示例(XML 风格):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <RollingFile name="File" fileName="logs/app.log"
                     filePattern="logs/app-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File"/>
        </Root>
    </Loggers>
</Configuration>

3.2.3 Logback —— 亲儿子

Logback 是 Ceki Gülcü 写的,他同时也是 Log4j 1.x 的作者。Logback 天生就是 SLF4J 的亲儿子,不需要任何桥接。性能不如 Log4j 2,但胜在稳定、配置简单,而且很多 Spring Boot 项目默认就是用它。我个人习惯在中小型项目里直接用 Logback,省心。

配置示例(logback.xml):

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
避坑指南:我曾经遇到过一个线上问题——Logback 的 <appender-ref> 顺序影响了日志写入性能。如果你配了多个 appender,注意别把异步 appender 放在同步前面,否则可能造成阻塞。

3.2.4 Java Util Logging (JUL) —— 自带但鸡肋

JDK 自带的 java.util.logging,功能太弱,配置方式也反人类。平时几乎没人直接用它,但有时候第三方库会用,需要靠 SLF4J 的桥接包来重定向。建议直接忽略。

3.3 Python logging —— 自成体系

Python 的 logging 模块是标准库的一部分,不需要额外装包。它的设计其实跟 Java 的门面+实现很像:Logger 负责对外接口,Handler 负责输出(文件、控制台、网络等),Formatter 负责格式化。只是没有拆成独立的门面库。

基本用法:

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# 控制台 Handler
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)

# 文件 Handler
fh = logging.FileHandler('app.log')
fh.setLevel(logging.ERROR)
fh.setFormatter(formatter)

logger.addHandler(ch)
logger.addHandler(fh)

logger.info("程序已启动")
logger.error("发生错误,请检查")
注意:Python logging 的 getLogger(name) 一般建议传入 __name__,这样日志器名字就跟模块路径一致,方便在日志里定位。我见过有些新手直接写 getLogger()——不传参,结果所有日志都混在根日志器里,排查时别提多痛苦了。

3.4 框架生态对比总览

特性 Log4j 1.x Log4j 2.x Logback Python logging
维护状态 EOL(已废弃) 活跃 活跃 标准库维护
异步性能 不支持 极高(Disruptor) 中(有 AsyncAppender) 低(可用 QueueHandler)
配置热加载 不支持 支持 支持 一般需自定义
与 SLF4J 集成 需桥接包 原生 原生 不适用
常见漏洞风险 高(已 EOL) 低(修复及时) 低 低

3.5 一张图看清日志框架生态

下面这张 SVG 图把 Java 和 Python 的日志框架分层关系画了出来。你看,SLF4J 作为统一的门面,上层对接应用代码,下层可以切换 Logback、Log4j2 或者 JUL。Python 的 logging 模块自己就是一整套,不需要额外门面。

日志框架生态分层图 应用代码 SLF4J 门面 Apache Commons Logging Python logging 模块 Logback Log4j 2.x JUL(java.util.logging) 说明 • 实线箭头:主流程;虚线箭头:Python logging 无需门面可直接使用。 • Logback 和 Log4j 2.x 均原生支持 SLF4J,无需桥接。 • JUL 也可通过 jul-to-slf4j 桥接到 SLF4J 门面。

总结一下:选框架时,我个人会先看项目语言——Java 的话,新项目直接 SLF4J + Logback 或 SLF4J + Log4j 2,看对性能的要求;Python 的话,用标准库 logging 就够了,最多加个第三方 Handler 比如 TimedRotatingFileHandler。别为了炫技引入一堆不成熟的框架,稳定才是第一位。