日志框架-1 jul与log4j与logback介绍

背景

日志框架一直没进行过细致的梳理,对其理解仅留在基本使用的程度;周五自测小功能时暴露了技术漏洞😦花费了不必要的时间,因此趁周末补习一下顺便做个总结。
本文主题内容是介绍Java中常用的日志框架解决方案,包括log4j+jcl以及logback+slf4j, 并介绍通过桥接和适配技术实现日志框架的兼容。文中会对日志框架进行拆分,分别介绍facade层和impl层以及其组合关系。

1.介绍

日志在软件的生命周期中起到重要作用,开发、测试、维护各个阶段都离不开日志。日志可以帮助developer快速追踪、定位问题,还可以用于性能监控以及后期的大数据分析。日志框架的作用自然是方便程序员记录日志。

1.1 日志发展过程

在这里插入图片描述
上图所示是日志框架的发展流程图,其中log4j和jcl以及log4j2来源于apache, slf4j和logback来源于qos公司,jul来自jdk; 其中log4j2在国内不常用,因此下文不进行介绍(可作为log4j的替代品)。

1.2 日志框架图

日志框架中大量使用了结构型设计模式,有外观、代理、桥接等。jcl和slf4j作为门面规范了接口, 同时对客户端隐藏了内部实现。
jcl通过代理在实现层有不同的实现方案,如log4j或jul;而slf4j和logback是qos公司一起推出的,因此slf4j的实现层默认是logback。

说明: 以下使用facade层指代jcl和slf4j,使用impl层指代log4j和jul和logback.

下图所示是Java的日志框架图,包含了facade层和impl层及其组合方案:
在这里插入图片描述
图中的实线表示可以通过组合形成日志解决方案, 包括:jcl+juc, jcl+log4j, slf4j+logback; 虚线表示可以借助对应的依赖包实现jul或log4j与slf4j的兼容.

2.facade层

章节1.2中图片可以表示为下图所示:
在这里插入图片描述
facade层用于定义接口规范,使得开发人员可以使用统一的API进行操作,而不用分别学习log4j、jul、logback以及新的日志框架对应的API等;其中:facede层包括jcl和slf4j, impl层有log4j、jul、logback, 可以通过组合、桥接、适配实现6种组合方案。

2.1 jcl

jcl是Apache为java日志框架推出的门面API,全称为"Jakarta Commons Logging",因为jcl属于Apache且代码位于org.apache.commons.logging包路径下,所以也可以称为"Apache Commons Logging".
pom依赖:

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

使用方式:

// 依赖apache.commons.logging包
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

// 获取日志对象,注意是Log类型
    private static final Log LOG = LogFactory.getLog(所在类类名.class);
    
    // 打印日志API
        LOG.trace("字符串类型");
        LOG.debug("test debug");
        LOG.info("test info");
        LOG.warn("test warn");
        LOG.error("test error");
        LOG.fatal("test fatal");
    }
}

jcl提供了6种日志级别,从低到高分别为:trace, debug, info, warn, error, fatal.
需要注意:Apache在1.2版本后已经停止了维护(2014),因此市面占用量较低。需要注意, 如果未给jcl指定impl层, 会默认使用jul.

2.2 slf4j

slf4j是qos为java日志框架推出的门面API,全称为“simple logging facade for java”, 是目前比较流行的日志框架.

pom依赖:

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

使用方式:

// 依赖org.slf4j包
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

	// 获取日志对象,注意是Logger类型
    private Logger LOGGER = LoggerFactory.getLogger(slf4jLogbackTest.class);
    
    	// 打印日志API
        LOGGER.trace("test trace");
        LOGGER.debug("test debug");
        LOGGER.info("test info");
        LOGGER.warn("test warn");
        LOGGER.error("test error");

slf4j提供5种日志级别,从低到高分别为:trace, debug, info, warn, error. 相比jcl少了fatal级别。
需要注意: 如果项目仅依赖slf4j而未指定impl层,启动时会因加载不到StaticLoggerBinder类而报错(该类在logback-classic中定义):
在这里插入图片描述

3.实现层

日志框架中真正起作用的是实现层,包括: juc、log4j、logback.

3.1 juc

juc是jdk提供的日志框架,因此不需要引入任何依赖;

package com.seong.test;

import org.junit.Test;
import java.util.logging.Logger;

public class JucTest {
    private static final Logger LOGGER = Logger.getLogger(JucTest.class.getName());

    @Test
    public void test() {
        LOGGER.finest("log finest");
        LOGGER.finer("log finer");
        LOGGER.fine("log fine");
        LOGGER.config("log config");
        LOGGER.info("log info");
        LOGGER.warning("log warning");
        LOGGER.severe("log severe");
    }
}

结果如下所示:
在这里插入图片描述
日志分层打印策略使得info级别以下日志未被打印(可在${JAVA_HOME}/jre/lib/logging.properties中修改);另外由于jul通过借助Sytem.err进行打印,因此显示红色字体。

3.2 log4j

引入log4j依赖包:

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

其中:版本1.2.17已是最新版本, 在2012后已停止维护.

添加配置文件log4j.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration>
    <appender name="log4jConsole" class="org.apache.log4j.ConsoleAppender">
    	...
    </appender>
    
    ...

    <root>
        <priority value ="info"/>
        <appender-ref ref="log4jConsole"/>
        ...
    </root>
</log4j:configuration>

未配置log4j.xml时, 打印日志会报错:
在这里插入图片描述

Java代码:

package com.seong;

import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jSingleTest {
    Logger LOGGER = Logger.getLogger(Log4jSingleTest.class);
    @Test
    public void test() {
        LOGGER.trace("test trace");
        LOGGER.debug("test debug");
        LOGGER.info("test info");
        LOGGER.warn("test warn");
        LOGGER.error("test error");
        LOGGER.fatal("test fatal");
    }
}

注意:这里单独使用log4j, 使用的Logger为org.apache.log4j包中的类.

3.3 logback

Logback包括以下3个部分:
[1] logback-core (基础核心, 被其他模块应用)
[2] logback-classic (完整实现SLF4J API接口)
[3] logback-access (提供通过Http访问日志的功能)

本章节中使用logback提供的日志记录能力, 而不需要提供http访问日志的能力, 因此不涉及logback-acess依赖;

引入依赖包:

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

这里引入的ogback-classic依赖同时包含了ogback-coreslf4j-api:
![在
可以看出logback包中已经依赖了slf4j, 即logback默认设置为与slf4j组合使用.

4.组合方案

4.1 log4j 与 jcl

引入依赖包:

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

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

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration>
    <appender name="log4jConsole" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n" />
        </layout>

        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <param name="levelMin" value="debug" />
            <param name="levelMax" value="warn" />
            <param name="AcceptOnMatch" value="true" />
        </filter>
    </appender>

    <appender name="log4jFile" class="org.apache.log4j.RollingFileAppender">
        <param name="File" value="/Users/seong/Documents/work/code/blog/logdemo/log/temp/log4jFile.log" />
        <!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
        <param name="Append" value="true" />
        <param name="MaxBackupIndex" value="10" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n" />
        </layout>
    </appender>

    <appender name="log4jDaily" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="/Users/seong/Documents/work/code/blog/logdemo/log/temp/log4jDaily.log" />
        <param name="DatePattern" value="'.'yyyy-MM-dd'.log'" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern"
                   value="[%d{MMdd HH:mm:ss SSS\} %-5p] [%t] %c{3\} - %m%n" />
        </layout>
    </appender>

    <root>
        <priority value ="info"/>
        <appender-ref ref="log4jConsole"/>
        <appender-ref ref="log4jFile"/>
        <appender-ref ref="log4jDaily"/>
    </root>
</log4j:configuration>

Java代码:

package com.seong;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;

public class JucLog4jTest {
    Log log = LogFactory.getLog(JucLog4jTest.class);
    @Test
    public void test() {
        log.trace("test trace");
        log.debug("test debug");
        log.info("test info");
        log.warn("test warn");
        log.error("test error");
        log.fatal("test fatal");
    }
}

4.2 logback和slf4j

引入依赖包:

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

添加logback.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scanPeriod="2 seconds" debug="true">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="/Users/seong/Documents/work/code/blog/logdemo/log/temp" />
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <appender name="logbackFile" class="ch.qos.logback.core.FileAppender">
        <file>${LOG_HOME}/logbackFile.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <appender name="logbackRollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/logbackRollFile.log</file>

        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_HOME}/logbackRollFile-%d{yyyy-MM-dd}.%i.log</FileNamePattern>

            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1kb</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>

            <MaxHistory>30</MaxHistory>
        </rollingPolicy>

    </appender>


    <logger name="com.seong.test.LogbackTest" level="warn" />

    <!-- 日志输出级别 -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="logbackRollFile" />
        <appender-ref ref="logbackFile" />
    </root>

</configuration>

注意: 没有配置文件时, 使用logback默认的日志风格.

Java代码:

package com.seong;

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

public class slf4jLogbackTest {
    private Logger LOGGER = LoggerFactory.getLogger(slf4jLogbackTest.class);

    @Test
    public void test() {
        LOGGER.trace("test trace");
        LOGGER.debug("test debug");
        LOGGER.info("test info");
        LOGGER.warn("test warn");
        LOGGER.error("test error");
    }
}

5.适配模式

slf4j除了可以作为logback的门面, 还可以作为jcl, jul, log4j的门面; 此时需要借助适配包实现,如下图所示:
在这里插入图片描述以下以slf4j+log4j为例进行介绍.
pom依赖:

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

slf4j-log4j12中包含了log4jslf4j-api, 因此不用再引入其他依赖, 如下图所示:
在这里插入图片描述
此时, 按章节4中配置文件配置好log4j后, 客户端即可以使用slf4j的API记录日志.

6.桥接模式

当依赖的第三方jar包使用不同的日志框架时, 需要通过桥接来实现兼容, 否则需要独立维护两套配置; 桥接的实现需要借助对应的依赖,如下图所示:
在这里插入图片描述
案例介绍:
项目使用主流的slf4j+logback作为日志框架, 依赖了一个使用log4j为日志框架的第三方jar包。
为保障程序正常运行, 项目需要维护两套日志规范, 即为logback和log4j各维护一份配置文件。 基于减少重复工作并提高项目的可维护性的角度, 希望logback和log4j共用一份配置文件, 即将log4j的日志打印任务桥接给logback.
第三方maven坐标:

<groupId>org.example</groupId>
<artifactId>jcl-log4j</artifactId>
<version>1.0-SNAPSHOT</version>

第三方jar包的pom依赖:

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

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

项目引用第三方:

 <dependency>
     <groupId>org.example</groupId>
     <artifactId>jcl-log4j</artifactId>
     <version>1.2</version>
    <exclusions>
           <exclusion>
               <artifactId>log4j</artifactId>
               <groupId>log4j</groupId>
           </exclusion>
       </exclusions>
 </dependency>

分析:
log4j-over-slf4j依赖中定义了与log4j-log4j完全相同的包、类、方法, 用于顶替log4j, 同时将来自门面的日志打印请求转发到logback上实现桥接; 因此, 使用时需要排除第三方包中的log4j依赖.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值