java日志框架详解

1. 常用日志框架

对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪,基于日志的业务逻辑统计分析等都离不日志。Java领域存在多种日志框架,目前常用的日志框架包括Log4j,Log4j 2,Commons Logging,Slf4j,Logback,Jul等。

1.1 Java常用日志框架类别

  • Slf4j: Simple Logging Facade for Java,缩写Slf4j。类似于Commons Logging,是一套简易Java日志门面,本身并无日志的实现。
  • Logback: 一套日志组件的实现(slf4j阵营)。
  • Commons Logging: Apache基金会所属的项目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更名为Commons Logging。
  • JUL: Java Util Logging,自Java1.4以来的官方日志实现。
  • Log4j: Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。 Log4j是几种Java日志框架之一。
  • Log4j2 : Apache Log4j 2是apache开发的一款Log4j的升级产品。
    看了上面的介绍是否会觉得比较混乱,这些日志框架之间有什么异同,都是由谁在维护? 下文会逐一介绍。

1.2 Java常用日志框架历史

  • 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j。后来Log4j成为Apache基金会项目中的一员。
  • 期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议sun引入Log4j到java的标准库中,但Sun拒绝了。
  • 2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。在JUL出来以前,log4j就已经成为一项成熟的技术,使得log4j在选择上占据了一定的优势。
  • 接着,Apache推出了Jakarta Commons Logging,JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是log4j,也可以是Java Util Logging
  • 后来(2006年),Ceki Gülcü不适应Apache的工作方式,离开了Apache。然后先后创建了slf4j(日志门面接口,类似于Commons Logging)和Logback(Slf4j的实现)两个项目,并回瑞典创建了QOS公司,QOS官网上是这样描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。
  • 现今,Java日志领域被划分为两大阵营:Commons Logging阵营和SLF4J阵营。
    Commons Logging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。2013年底有人分析了GitHub上30000个项目,统计出了最流行的100个Libraries,可以看出slf4j的发展趋势更好。
    在这里插入图片描述
  • Apache眼看有被Logback反超的势头,于2012-07重写了log4j 1.x,成立了新的项目Log4j 2。Log4j 2具有logback的所有特性。

1.3 两大日志接口阵营

  • Apache公司研发的Jakarta Commons Logging ,简称 Commons Logging 接口或者JCL
  • 由从Apache跳槽的Ceki Gülcü 研发的Simple Logging Facade for Java,简称 slf4j接口。
1.3.1 基于Commons Logging 接口实现的常用日志框架
  • JUL
  • Log4j
1.3.2 基于SLF4J接口实现的常用日志框架
  • Logback
  • Log4j
  • JUL
  • 其他如下图所示:

在这里插入图片描述

2. 日志框架的使用

2.1 SLF4J接口及其具体实现的使用

2.1.1 首先添加slf4j-api-1.7.26.jar到classpath,maven项目可以用maven坐标,非maven项目可以直接下载。
比如使用maven时:

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

就会自动加载该jar包到项目中。不用去网上下载。如下图所示。
在这里插入图片描述

2.2.2 其次是添加具体实现。

  • slf4j-log4j12-1.7.26.jar
    绑定log4j version 1.2,一个广泛使用的日志框架。可以手动下载也可以使用maven下载。推荐使用maven。方便。
<dependency> 
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-log4j12</artifactId>
 <version>1.7.26</version>
</dependency>

在这里插入图片描述
在pom.xml文件中添加该依赖之后,会自动下载三个jar包,一个是slf4j-log4j12-1.7.26.jar,另外两个是slf4j-api-1.7.26.jar,还有一个就是具体实现的log4j-1.2.17.jar。只要添加这个依赖,就会下载相关的jar包,不用重复添加slf4j-api-1.7.26.jar的依赖了。

  • slf4j-jdk14-1.7.26.jar
    绑定java.util.logging。可以手动下载也可以使用maven下载。推荐使用maven。方便。
<dependency> 
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-jdk14</artifactId>
 <version>1.7.26</version>
</dependency>

在这里插入图片描述
由上图可以看到。会自动加载slf4j-api-1.7.26.jar以及slf4j-jdk4-1.7.26.jar到项目中。具体实现是由JVM来实现的。故没有额外的具体实现的jar包。

  • logback-classic-1.2.3.jar (不使用maven的话,还需要添加一个 logback-core-1.2.3.jar)
    Logback类是SLF4J 接口的直接实现。因此,将SLF4J与logback结合使用会严格限制零内存和计算开销。
<dependency> 
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

在这里插入图片描述
如上图所示,该依赖会自动下载slf4j-api-1.7.25.jarlogback-classic-1.2.3.jar 以及 logback-core-1.2.3.jar

  • slf4j-jcl-1.7.26.jar
    绑定Jakarta Commons Logging。此绑定将委派所有SLF4J日志记录到JCL。
    在这里插入图片描述
    如上图所示,该依赖会自动下载slf4j-jcl-1.7.26.jarslf4j-jcl-1.7.26.jar 以及 commons-logging-1.1.1.jar
  • slf4j-nop-1.7.26.jar
    绑定NOP,静默丢弃所有日志记录。
  • slf4j-simple-1.7.26.jar
    绑定一个简单的实现,将所有事件输出到System.err。仅打印I​​NFO级别以上的消息。此绑定在小应用程序的上下文中可能很有用。

要切换基于SLF4J实现的日志框架,只需替换类路径上的slf4j绑定。例如,要从java.util.logging切换到log4j,只需将slf4j-jdk14-1.7.26.jar替换为slf4j-log4j12-1.7.26.jar即可。
后面3种官网没有给maven依赖,只能下载相关jar包,并配置classpath。

在项目中如果用slf4j-api作为日志门面,有多个日志实现组件同时存在,例如同时存在Logback,slf4j-log4j12,slf4j-jdk14,slf4j-jcl四种实现,则在项目实际运行中,Slf4j的绑定选择绑定方式将有Jvm确定,并且是随机的,这样会和预期不符,实际使用过程中需要避免这种情况。

2.2 JCL接口及其具体实现的使用

因本人没用过JCL,具体使用细节请参考这篇博客。 https://blog.csdn.net/backbug/article/details/78655664

3. 日志框架之间的兼容

在实际环境中我们经常会遇到不同的组件使用的日志框架不同的情况,例如Spring Framework使用的是日志组件是log4j,XSocket依赖的则是Java Util Logging。当我们在同一项目中使用不同的组件时应该如果解决不同组件依赖的日志组件不一致的情况呢?现在我们需要统一日志方案,统一使用SLF4J,把他们的日志输出重定向到SLF4J,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具。因此我们可以先将log4j输出重定向到slf4j,再把slf4j结果输出到与其绑定的jul上面。Slf4j带有几个桥接模块,可以重定向log4j,JCL和java.util.logging中的API到Slf4j。
在这里插入图片描述
在这里插入图片描述
上图给出了三种桥接方式已经对应的jar包。
没有将logback的输出重定向到slf4j的jar包?
可能因为logback是Native实现吧。

4. 使用Slf4j桥接注意事项

在使用Slf4j桥接时要注意避免形成死循环,在项目依赖的jar包中不要存在以下情况。
在这里插入图片描述

5. 总结

推荐在项目中使用SLF4J+logback的方式实现日志框架。

  • Slf4j实现机制决定Slf4j限制较少,使用范围更广。由于Slf4j在编译期间,静态绑定本地的LOG库使得通用性要比Commons
    logging要好。
  • Logback拥有更好的性能。Logback声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在Logback中需要3纳秒,而在Log4J中则需要30纳秒。LogBack创建记录器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒,而Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的。
  • Commons Logging开销更高 在使Commons Logging时为了减少构建日志信息的开销,通常的做法是:
    if(log.isDebugEnabled()){ log.debug(“User name: " + user.getName() +
    " buy goods id :” + good.getId()); } 在Slf4j阵营,你只需这么做: log.debug(“User
    name:{} ,buy goods id :{}”, user.getName(),good.getId());
    也就是说,slf4j把构建日志的开销放在了它确认需要显示这条日志之后,减少内存和cup的开销,使用占位符号,代码也更为简洁
  • Logback文档免费。Logback的所有文档是全面免费提供的,不象Log4J那样只提供部分免费文档而需要用户去购买付费文档。

我们在项目中加入以下依赖即可。

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

添加完依赖后,还需要编写logback的配置文件添加配置文件logback.xml才可以run。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义日志文件的存储地址 -->
    <!--
        关于catalina.base解释如下:
            catalina.home指向公用信息的位置,就是bin和lib的父目录。
            catalina.base指向每个Tomcat目录私有信息的位置,就是conf、logs、temp、webapps和work的父目录。
    -->
    <property name="LOG_DIR" value="${catalina.base}/logs"/>

    <!--
        %p:输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
        %r:输出自应用启动到输出该日志讯息所耗费的毫秒数
        %t:输出产生该日志事件的线程名
        %f:输出日志讯息所属的类别的类别名
        %c:输出日志讯息所属的类的全名
        %d:输出日志时间点的日期或时间,指定格式的方式: %d{yyyy-MM-dd HH:mm:ss}
        %l:输出日志事件的发生位置,即输出日志讯息的语句在他所在类别的第几行。
        %m:输出代码中指定的讯息,如log(message)中的message
        %n:输出一个换行符号
    -->
    <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level  %msg%n"/>


    <!--
        Appender: 设置日志信息的去向,常用的有以下几个
            ch.qos.logback.core.ConsoleAppender (控制台)
            ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件)
            ch.qos.logback.core.FileAppender (文件)
    -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 字符串System.out(默认)或者System.err -->
        <target>System.out</target>
        <!-- 对记录事件进行格式化 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <appender name="SQL_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建 -->
        <file>${LOG_DIR}/sql_info.log</file>
        <!-- 当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名。属性class定义具体的滚动策略类 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 必要节点,包含文件名及"%d"转换符,"%d"可以包含一个java.text.SimpleDateFormat指定的时间格式,默认格式是 yyyy-MM-dd -->
            <fileNamePattern>${LOG_DIR}/sql_info_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>20MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每个月滚动,如果是6,则只保存最近6个月的文件,删除之前的旧文件 -->
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <!-- 用于配置符合过滤条件的操作 ACCEPT:日志会被立即处理,不再经过剩余过滤器 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 用于配置不符合过滤条件的操作 DENY:日志将立即被抛弃不再经过其他过滤器 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="SQL_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/sql_error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_DIR}/sql_error_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>20MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="APP_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_DIR}/info.%d{yyyy-MM-dd}.log
            </FileNamePattern>
        </rollingPolicy>
        <encoder>
            <Pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%thread] [%logger:%line]--%mdc{client} %msg%n</Pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <property name="pattern" value="%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level  %msg%n"/>
                <pattern>%d{yyyyMMdd:HH:mm:ss.SSS}%thread%-5level%F{32}%M%L%msg</pattern>
            </layout>
        </encoder>
        <file>${LOG_DIR}/test.html</file>
    </appender>


    <!--
        用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
        <loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性
        name:
            用来指定受此logger约束的某一个包或者具体的某一个类。
        level:
            用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
            如果未设置此属性,那么当前logger将会继承上级的级别。
        additivity:
            是否向上级loger传递打印信息。默认是true。
        <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
    -->
    <logger name="java.sql" level="info" additivity="false">
        <level value="info" />
        <appender-ref ref="STDOUT"></appender-ref>
        <appender-ref ref="SQL_INFO"></appender-ref>
        <appender-ref ref="SQL_ERROR"></appender-ref>
    </logger>

    <logger name="com.souche.LogbackTest" additivity="false">
        <level value="info" />
        <appender-ref ref="STDOUT" />
        <appender-ref ref="APP_INFO" />
        <appender-ref ref="FILE"/>
    </logger>


    <!--
        也是<logger>元素,但是它是根logger。默认debug
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。
    -->
    <root level="info">
        <level>info</level>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SQL_INFO"/>
        <appender-ref ref="SQL_ERROR"/>
        <appender-ref ref="FILE"/>
    </root>

</configuration>

参考链接:
https://www.cnblogs.com/chenhongliang/p/5312517.html
https://www.slf4j.org/manual.html
https://www.jianshu.com/p/b3dedb8fb61e

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值