Java日志体系

1 日志系统

在应用程序的开发中,通常会使用日志记录监视代码中变量的变化情况,输出到控制台,文件系统或者网络。记录日志目的在于线上问题追踪,作为其他系统进行统计分析的"数据".

2 Java常用日志框架

Java 的日志框架有很多,比如:JUL(Java Util Logging)、Log4j、Logback、Log4j2、Tinylog 等。除此之外,还有 JCL(Apache Commons Logging)和 SLF4J 这样的“门面日志”

2.1 门面日志

什么是“门面日志”。“门面日志”利用了设计模式中的门面模式思想,对外提供一套通用的日志记录的 API,而不提供具体的日志输出服务,如果要实现日志输出,需要集成其他的日志框架,比如 Log4j、Logback、Log4j2 等。

这种门面模式的好处在于,记录日志的 API 和日志输出的服务分离开,代码里面只需要关注记录日志的 API,通过 SLF4J 指定的接口记录日志;而日志输出通过引入 JAR 包的方式即可指定其他的日志框架。当我们需要改变系统的日志输出服务时,不用修改代码,只需要改变引入日志输出框架 JAR 包。

2.2 日志框架的历史
  • 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j(作者Ceki Gülcü)。后来Log4j成为Apache基金会项目中的一员。
  • 期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议Sun引入Log4j到java的标准库中,但Sun拒绝了.
  • 2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。但是如果有人想换成其他日志组件,如log4j换成JUL,因为api完全不同,就需要改动代码。
  • Apache见此,开发了JCL(Jakarta Commons Logging),即commons-logging-xx.jar。它只提供一套通用的日志接口api,并不提供日志的实现。很好的设计原则嘛,依赖抽象而非实现。这样应用程序可以在运行时选择自己想要的日志实现组件。
  • 这样看上去也挺美好的,但是log4j的作者觉得JCL不好用,自己开发出slf4j,它跟JCL类似,本身不替供日志具体实现,只对外提供接口或门面。目的就是为了替代JCL。同时,还开发出logback,一个比log4j拥有更高性能的组件,目的是为了替代log4j。
  • Apache参考了logback,并做了一系列优化,推出了log4j2

3 commons-logging

3.1 概述

Apache Commons Logging是一个门面日。代码里面只需要关注记录日志的 API,通过JCL指定的接口记录日志;而日志输出通过引入 JAR 包的方式实现。如果没有引入日志框架,Apache Commons Logging会使用内部默认实现 SimpleLog

JUL最后更新于2014年7月,现以停止更新。

image

3.2 包结构

image
  • Log:日志对象接口,封装了操作日志的方法,定义了日志操作的5个级别:trace < debug < info < warn < error
  • LogFactory:抽象类,日志工厂,获取日志类;
  • LogFactoryImpl:LogFactory的实现类,真正获取日志对象的地方;
  • Log4JLogger:对log4j的日志对象封装;
  • Jdk14Logger:对JDK1.4的日志对象封装;
  • Jdk13LumberjackLogger:对JDK1.3以及以前版本的日志对象封装;
  • SimpleLog:commons-logging自带日志对象;

3.2 日志框架加载

jcl可以通过在ClassPath下创建commons-logging.properties配置文件指定加载日志实现框架。

#指定日志对象:
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Jdk14Logger
#指定日志工厂:
org.apache.commons.logging.LogFactory = org.apache.commons.logging.impl.LogFactoryImpl

jcl如果没有指定日志实现框架则默认加载时按照顺序log4j>jul> simpleLog依次加载,如果查找到log4j包则使用log4j,如果没有依次类推。

3.3 JCL使用

引用JCL依赖包

    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>

测试代码

此时由于项目也没有放入其他日志框架依赖包,会按照顺序log4j>jul> simpleLog加载,因此此时会使用jdk自带jul实现日志打印。

public class commons_loggingDemo {
    Log log= LogFactory.getLog(commons_loggingDemo.class);
    @Test
    public void test() throws IOException {
        log.debug("Debug info.");
        log.info("Info info");
        log.warn("Warn info");
        log.error("Error info");
        log.fatal("Fatal info");
    }
}

引用log4j依赖包

在不修改代码的前提下,引入log4j依赖包。在次调用测试代码,此时会使用log4j实现日志打印。

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

4 SLF4J

4.1 概述

SLF4J同样是一个门面日志。代码里面只需要关注记录日志的 API,通过SLF4J指定的接口记录日志;而日志输出通过引入 JAR 包的方式实现。

image
  • slf4j-api.jar 表示SLF4J提供的日志接口相关的jar包

  • natvie implementation of slf4j-api: 表示SLF4J本地底层日志框架,其中包括logback-classic.jar + logback-core.jar作为logback日志框架底层实现,SLF4J-Simple作为SLF4J简单日志框架实现,slf4j-nop.jar表示空实现。

  • none-natvie implementation of slf4j-api :表示非本地,外部日志框架。其中包括lo4j.jar表示log4j日志框架,JUL表示JDK自带日志框架,log4j-core.jar表示log4j2日志框架(图中没画)

  • Adaptation layer:表示外部日志框架的适配器

组合如下

  • 使用logback作为日志框架
    • slf4j-api.jar + logback-classic.jar + logback-core.jar
  • 使用log4j作为日志框架
    • slf4j-api.jar + slf4j-log4j12.jar + log4j.jar
  • 使用jul作为日志框架
    • slf4j-api.jar + slf4j-jdk14.jar
  • 使用log4j2作为日志框架
    • slf4j-api.jar + +log4j-slf4j-impl+log4j-core
  • 只用slf4j无日志实现
    • slf4j-api.jar + slf4j-nop.jar
4.2 桥接器

在实际开发过程中通常会遇到下面的问题,项目中旧模块使用的是log4j来打印日志的话,而新模块想用logback来统一打印日志的话,由于两者并不兼容,如果直接修改业务代码会存在一定风险。那么该如何解决呢?

SLFJ提供了各种日志框架的桥接器,我们只需要引用对应适配器的实现包,并去掉原始的依赖包,SLFJ会自动帮我们完成日志框架的统一。上面案例中可以去掉log4j包log4j.jar引入log4j-over-slf4j包(这个包使之前代码使用log4j不会因为找不到而报错,并会将打印日志桥接给slf4j-api.jar),并同时引入slf4j-api.jar + logback-classic.jar + logback-core.jar。

image

SLFJ提供的桥接器

image
  • JCL桥接器
    • 使用jcl-over-slf4j.jar
  • log4j桥接器
    • 使用log4j-over-slf4j.jar
  • JUL桥接器
    • 使用jul-to-slf4j.jar
4.3 小结
image
4.4 使用

springBoot默认使用logback作为日志框架,使用slf4j作为门面日志。

使用springBoot+slf4j+logback

<dependencyManagement>
    <dependencies>
      <dependency>
        <!-- Import dependency management from Spring Boot -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>1.5.18.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
  </dependencies>
springbootSJ4J默认Logback.jpg

这里已经帮助我们添加各种类型日志桥接器,如果我们我们向将原来项目中日志框架去掉,并使用Logback框架时。只需要去掉原先依赖。

使用springBoot+slf4j+log4j

这里需要添加spring-boot-starter-log4j依赖,并将spring-boot-starter-logging从依赖中排除。

<dependencyManagement>
    <dependencies>
      <dependency>
        <!-- Import dependency management from Spring Boot -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>1.5.18.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <exclusions>
        <exclusion>
          <artifactId>spring-boot-starter-logging</artifactId>
          <groupId>org.springframework.boot</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j</artifactId>
      <version>1.3.8.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
  </dependencies>
image

同样这里已经帮助我们添加各种类型日志桥接器,如果我们我们向将原来项目中日志框架去掉,并使用logb4j框架时。只需要去掉原先依赖。

使用springBoot+slf4j+log4j2

这里需要添加spring-boot-starter-log4j2依赖,并将spring-boot-starter-logging从依赖中排除。

<dependencyManagement>
    <dependencies>
      <dependency>
        <!-- Import dependency management from Spring Boot -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>1.5.18.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <exclusions>
        <exclusion>
          <artifactId>spring-boot-starter-logging</artifactId>
          <groupId>org.springframework.boot</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
  </dependencies>
image

同样这里已经帮助我们添加各种类型日志桥接器,如果我们我们向将原来项目中日志框架去掉,并使用logb4j框架时。只需要去掉原先依赖。

关于其他使用日志框架可以排除spring-boot-starter-logging后自行添加就不再详诉。

测试代码

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class Sj4jTest {

    Logger logger = LoggerFactory.getLogger("Sj4jTest");
    
    @Test
    public void test() throws IOException {
        logger.debug("Debug info.");
        logger.info("Info info");
        logger.warn("Warn info");
        logger.error("Error info");
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容