探析Java日志框架

简介:         目前,几乎所有的应用程序中,都会用到日志框架来记录程序的运行信息。日志虽然不影响应用程序的运行结果,但是没有日志的应用程序是不健全,不完整的。良好的日志系统可以帮助我们快速的定位到程序问题,包括近几年火起来的日志分析系统,比如ELK,日志在我们系统中被重视起来,也起到了举足轻重的作用

        几乎所有的应用程序中,都会用到日志框架来记录程序的运行信息。日志虽然不影响应用程序的运行结果,但是没有日志的应用程序是不健全,不完整的。良好的日志系统可以帮助我们快速的定位到程序问题,还可以帮助我们分析系统的缺陷,比如近几年火起来的ELK。目前主流的日志框架也挺多,然而回顾以往,在开发过程中也没有深入了解,只是简单的引入一个依赖包,编码时log打印就完事了,不禁感慨,工具做的越来越便捷好用的同时,我们也不要忘了去探究更深层次的实现。

        本文以探究java日志框架体系为出发点,回顾一下日志系统的发展历程,了解一下目前主流的几种日志框架的区别及特性,最后从使用的角度分析日志框架在系统中的应用。

参考链接

意义

  • 开发调试: 在合适代码位置打印日志,并将程序运行中的一些参数信息也打印出来,可以更快的定位问题,也能在解决时提供必要的上下文信息。
  • 系统维护: 记录大部分的异常信息,通过收集日志信息可以对系统的运行状态进行实时监控预警。
  • 数据分析: 配合日渐成熟的大数据技术,对海量日志进行分析,可以获取有助于战略角色的信息,比如最火的ELK框架。

发展历程

        其实Java的日志框架体系是很混乱的。早期 Java 日志框架没有制定统一的标准,开发时经常会引入第三方的库,这第三方库中又可能使用了不同的日志框架,那么这个应用程序中其实包含了多种日志框架。

        比如经常会遇到,在项目启动的时候会出现以下红色字体的日志,提示有多个日志框架绑定到了slf4j,就是因为找到了多个日志框架的实现。关于这个问题如何解决会在后文中提到。

image.png

接下来看一下日志框架都有哪些,以及发展过程中经历了哪些阶段

image.png

        在 jdk1.3 之前,还没有内置的日志功能,只能使用原始的 System.out.println(), System.err.println()或者 e.printStackTrace()。通过把 debug 日志写到 StdOut 流,错误日志写到 ErrOut 流,以此记录应用程序的运行状态。这种原始的日志记录方式缺陷明显。后来慢慢出现了各种日志框架。整个发展历程也经历了如下五个阶段。

(一)Log4j
        Apache基金会的一个项目。1999年由Ceki Gülcü 创建,Log4j几乎成为了Java日志框架的实际标准。

(二)JUL
        Log4j 作为 Apache 基金会的一员,Apache 希望将 Log4j 引入 jdk,不过被 sun 公司拒绝了。随后,sun 模仿 Log4j,在 jdk1.4 中引入了 JUL(java.util.logging)。

(三)Commons Logging
        如果有更换日志组件的想法,如 log4j 换成 JUL,因为 API 完全不同,就需要改动代码。为了将日志接口与实现解耦,2002 年 Apache 推出了 JCL(Jakarta Commons Logging)。Commons Logging 定义了一套日志接口,具体实现则由 Log4j 或 JUL 来完成。Commons Logging 基于动态绑定来实现日志的记录,在使用时只需要用它定义的接口编码即可,程序运行时会使用 ClassLoader 寻找和载入底层的日志库,因此可以自由选择由 log4j 或 JUL 来实现日志功能。

(四)Slf4j & Logback
        Ceki Gülcü 与 Apache 基金会关于 Commons-Logging 制定的标准存在分歧,后来, Ceki Gülcü 离开 Apache 并先后创建了 Slf4j 和 Logback 两个项目。Slf4j 是一个日志门面,只提供接口,可以支持 Logback、JUL、log4j 等日志实现,Logback 提供具体的实现,它相较于 log4j 有更快的执行速度和更完善的功能。当前分为三个目标模块:

  • logback-core:核心模块,是其它两个模块的基础模块
  • logback-classic:是log4j的一个改良版本,同时完整实现 SLF4J API,可以很方便地更换成其它日记系统如log4j 或 JDK Logging
  • logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能,是logback不可或缺的组成部分

(五)Log4j2
        为了维护在 Java 日志江湖的地位,防止 JCL、Log4j 被 Slf4j、Logback 组合取代 ,2014 年 Apache 推出了 Log4j 2。Log4j 2 与 log4j 不兼容,经过大量深度优化,其性能也有显著提升。

分类

        上文中提到的日志框架有 Log4j,Log4j2,Commons Logging,Slf4j,Logback,JUL。可以分为两种类型:门面日志日志系统

  • 日志门面: 只提供日志相关的接口定义,即相应的 API,而不提供具体的接口实现。日志门面在使用时,可以动态或者静态地指定具体的日志框架实现,解除了接口和实现的耦合,使用者可以灵活地选择日志的具体实现框架。
  • 日志系统: 与日志门面相对,它提供了具体的日志接口实现,应用程序通过它执行日志打印的功能。

image.png

框架

        在整个日志框架中主要包括日志门面、日志适配器、日志库三个部分,它们之间的关系如下图所示:

image.png

日志门面

        使用日志门面的目的就是将接口和实现解耦,使用的就是设计模式中的门面模式,而门面模式的核心思想就是: 外部客户端与一个子系统的通信,必须通过一个统一的外观对象进行,使得子系统更易于使用,其本质就是为子系统中的一组接口提供一个统一的高层接口, 在这种日志门面下,使用者可以随意切换底层使用的日志系统了。

        比如有这样的使用场景:系统开发时使用的是 Logback,引入了第三方A依赖,A依赖中使用的日志系统为 Log4j,又引入了 B.jar,而 B.jar 中使用的日志系统为 JUL。

        在这种场景下,如果每使用到一种日志系统,我们的系统都需要同时支持和维护的话,其繁琐程度不言而喻。为了解决这个问题,可以引入一个适配层,由适配层决定具体使用哪一种日志系统,应用程序中的调用者只管打印日志,而不必关心日志是如何被打印出来的,这样就避免了需要维护复杂日志系统的问题。Slf4j 和 Commons-Logging 就是适配层,而 JUL、Log4j 和 Logback 等就是打印日志的具体实现。

日志适配器

        Slf4j 的作者 Ceki Gülcü 当年因为觉得Commons-Logging的API设计以及性能都不够好,因而设计了 Slf4j。为了 Slf4j 能够兼容各种类型的日志系统实现,还设计了相当多的 adapter 和 bridge 来连接:

image.png

日志库

        日志库就是对日志门面中接口的具体实现,市面上的日志库有很多,比如log4j2,logback等等。比较常用的组合使用方式是:

  • Slf4j 与 Logback 组合使用,Logback 也必须配合 Slf4j 使用。由于 Logback 和 Slf4j 是同一个作者,兼容性非常好。
  • Commons Logging 与 Log4j 组合使用。

        看上面的组合方式也看得出来,一个门面搭配一个日志库使用,那为什么会出现两个门面呢?其实曾经Apache 试图说服 Log4j 以及其它的日志来按照 Commons-Logging 的标准编写,但是由于 Commons-Logging 的类加载机制在实际应用中存在问题(它使用 ClassLoader 寻找和载入底层的日志库),实现起来也不友好,因此 Log4j 的作者便开发了 Slf4j,与 Commons-Logging 两分天下。

对于上面两种组合方式,logback对比log4j有如下的几个优点:

  1. Slf4j 实现机制决定 Slf4j 限制较少,使用范围更广。相较于 Commons-Logging,Slf4j 在编译期间便静态绑定本地的 Log 库,其通用性要好得多。
  2. Logback 拥有更好的性能。Logback 声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高,这个操作在 Logback 中只需 3 纳秒,而在 Log4j 则需要 30 纳秒。
  3. Slf4j 支持参数化,可以使用{}占位符
  4. Logback 的所有文档是免费提供的,Log4j 只提供部分免费文档而需要用户去购买付费文档。
  5. MDC (Mapped Diagnostic Contexts) 用 Filter,将当前用户名等业务信息放入MDC 中,在日志 format 定义中即可使用该变量。具体而言,在诊断问题时,通常需要打出日志。如果使用 Log4j,则只能降低日志级别,但是这样会打出大量的日志,影响应用性能;如果使用 Logback,通过配置log的pattern即可将MDC中的业务信息跟随日志打印在同一行。

应用

如果是新项目使用日志框架,建议使用 Slf4j 与 Logback 组合,可通过如下配置进行集成:

<dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.0-alpha1</version>
</dependency>

<dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.10</version>
</dependency>
            
<dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.10</version>
</dependency>

        对于已有工程,需要根据所使用的日志库来确定门面适配器从而使用 Slf4j。Slf4j 的设计思想比较简洁,使用了 Facade 设计模式,Slf4j 本身只提供了一个 slf4j-api.jar 包,主要是日志的抽象接口,jar 包中本身并没有对抽象出来的接口做实现。对于不同的日志实现方案(例如 Logback,Log4j 等),封装出不同的桥接组件(例如 logback-classic-version.jar,slf4j-log4j12-version.jar),这样使用过程中可以灵活地选取自己项目里的日志实现。

举例说明,如果已有工程中使用了 Log4j 日志库,可通过如下配置进行集成:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.0-alpha1</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.32</version>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

Slf4j 与其它日志组件调用关系图如下:

image.png

具体的接入方式如下:

image.png

        如果在同一项目中使用不同的组件时,会出现不同组件依赖的日志组件不一致的情况,这就需要统一日志方案,统一使用 Slf4j,把他们的日志输出重定向到 Slf4j,然后 Slf4j 又会根据绑定器把日志交给具体的日志实现工具。Slf4j 带有几个桥接模块,可以重定向 Log4j,JCL 和 java.util.logging 中的 Api 到 Slf4j。

举例说明:如果老代码中直接使用了 Log4j 日志库接口打印日志,需引入如下配置:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.32</version>
</dependency>

桥接方式如下:

image.png

常见问题

日志组件冲突

image.png

        如第二章提到的那个问题,Spring 本身的日志实现使用了 Commons Logging,如果想使用 Slf4j+Logback 组合,这时候需要在项目中将 Commons Logging 排除掉,通常会用到以下 3 种方案,各有利弊,可以根据项目的实际情况选择最适合自己项目的解决方案:

  • 方案一:采用 maven 的 exclusion 方案,如果有多个组件都依赖了 commons-logging,则需要在很多处增加 exclusion,比较繁琐。
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.14</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  • 在 maven 声明 commons-logging 的 scope 为 provided,在调试代码时有可能导致 IDE 将 commons-logging 放置在 classpath 下,从而导致程序运行时出现异常。
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
    <scope>provided</scope>
</dependency>
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
算法 Java 数据处理
Java集合框架的优缺点
Java集合框架的优缺点
|
1月前
|
运维 监控 Java
推荐一款好用的Java分布式任务调度框架!
推荐一款好用的Java分布式任务调度框架!
168 0
|
4天前
|
Java Maven 开发工具
《Java 简易速速上手小册》第5章:Java 开发工具和框架(2024 最新版)
《Java 简易速速上手小册》第5章:Java 开发工具和框架(2024 最新版)
25 1
|
8天前
|
Java 大数据 云计算
Spring框架:Java后台开发的核心
【4月更文挑战第15天】Spring框架在Java后台开发中占据核心位置,因其控制反转(IoC)、面向切面编程(AOP)、事务管理等特性提升效率和质量。Spring提供数据访问集成、RESTful Web服务和WebSocket支持。优势包括高效开发、灵活扩展、强大生态圈和广泛应用。应用于企业级应用、微服务架构及云计算大数据场景。掌握Spring对Java开发者至关重要。
|
11天前
|
存储 Java 编译器
Java集合丛林:深入了解集合框架的秘密
Java集合丛林:深入了解集合框架的秘密
15 0
Java集合丛林:深入了解集合框架的秘密
|
14天前
|
存储 Java 数据库连接
java使用mp持久化框架,写入5000个字符,但是VARCHAR(255) 会报错
使用Java的MyBatis Plus框架时,如果尝试将超过VARCHAR(255)限制的字符串(如5000个字符)存入数据库,会抛出异常。解决方法是将列类型改为TEXT。可通过在实体类属性上添加`@TableField(typeHandler = JdbcType.CLOB)`注解,如`private String content;`,将属性映射到CLOB类型列,以存储更长字符串。
9 0
|
15天前
|
存储 Java
java反射——设计框架的灵魂
java反射——设计框架的灵魂
|
20天前
|
前端开发 安全 Java
使用Java Web框架:Spring MVC的全面指南
【4月更文挑战第3天】Spring MVC是Spring框架的一部分,用于构建高效、模块化的Web应用。它基于MVC模式,支持多种视图技术。核心概念包括DispatcherServlet(前端控制器)、HandlerMapping(请求映射)、Controller(处理请求)、ViewResolver(视图解析)和ModelAndView(模型和视图容器)。开发流程涉及配置DispatcherServlet、定义Controller、创建View、处理数据、绑定模型和异常处理。
使用Java Web框架:Spring MVC的全面指南
|
22天前
|
Java 关系型数据库 数据库连接
52 类 110 个常用 Java 组件和框架整理
52 类 110 个常用 Java 组件和框架整理
15 0
|
26天前
|
监控 Java 测试技术
日志框架到底是Logback 还是 Log4j2
日志框架到底是Logback 还是 Log4j2
18 0

热门文章

最新文章