首页 > 编程语言 >Java日志框架的依赖设置备查(SLF4J, Log4j, Logback)

Java日志框架的依赖设置备查(SLF4J, Log4j, Logback)

时间:2023-08-26 16:23:54浏览次数:37  
标签:Java slf4j Log4j SLF4J logging org 日志 Logback log4j

example

前言

最近在看代码的过程中,发现身边的许多人在使用Java日志框架时,对于应该引入何种依赖不甚了解,搜索网络上的文章,常常也是互不一致。这篇文章可以看着是Java日志框架的入门使用和实践建议,重点介绍不同组合方式下的依赖设置及其背后的逻辑,一方面给自己备查,另外也希望对小伙伴们有所帮助。

Java日志框架家族繁杂,出于实用的原则,这里主要介绍主流的几个项目:SLF4J、Logback、Log4j 2,以及它们之间各种搭配用法和使用建议。

另外,由于Log4j 1项目已经在2015-08-05正式宣布死亡(最终版本停留在2012-05-13发布的1.2.17),因此这里也不再讨论Log4j 1,下文所有提到Log4j的地方,都是指Log4j 2。



简述

对于日志框架,可以按照「分层」的概念来理解:接口层、实现层。开发者在使用日志框架时,建议基于接口层而非实现层进行开发,这样的好处是,避免项目与某一个具体日志框架耦合,这是一个常见的编程理念,应该比较容易理解。

例如,项目最初使用SLF4J作为接口层,使用Logback作为实现层,而你的项目代码中使用的也是接口层的类org.slf4j.Logger,这种情况下,当将来你想将实现层切换为Log4j时,你最需要改动依赖项,而不需要改动代码。

但是,如果你最初的项目代码中使用的并非是接口层的类,而是实现层(即Logback)的类ch.qos.logback.classic.Logger(这可能是因为手滑,毕竟类名都是一样的)。这种情况下,想要切换实现层,就需要改动所有涉及使用到这个类的代码。



SLF4J + Logback

依赖设置

由于logback-classic中既有实现层,也包含了对接口层SLF4J的依赖,因此,最简单的设置可以是这样的:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.12</version>
    <scope>runtime</scope>
</dependency>

不过,就像简述里说的,为了避免开发者不小心误用实现类ch.qos.logback.classic.Logger,推荐使用如下的依赖设置,注意其中的scope设置:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.12</version>
    <scope>runtime</scope>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

配置文件(logback.xml)

这里给出一个最常见的配置文件,包含控制台输出、滚动文件输出:

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

    <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>

        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/app-%d{yyyy-MM-dd-HH}-%i.log</fileNamePattern>
            <!-- 单个日志文件超过10M,则进行滚动,对文件进行递增编号(即%i) -->
            <maxFileSize>10MB</maxFileSize>
            <!-- 所有日志文件的大小限制,超出则删除旧文件 -->
            <totalSizeCap>5GB</totalSizeCap>
            <!-- 与fileNamePattern相结合,本例中由于时间粒度是小时,因此这里表示保存48个小时 -->
            <maxHistory>48</maxHistory>
        </rollingPolicy>

        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="ROLLING_FILE" />
    </root>
</configuration>

代码示例

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
private static final Logger logger = LoggerFactory.getLogger(App.class);
...
logger.info("First name: {}, last name: {}", firstName, lastName);




SLF4J + Log4j

依赖设置

由于log4j-slf4j-impl中既有实现层,也包含了对接口层SLF4J的依赖,因此,最简单的设置可以是这样的:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.20.0</version>
</dependency>

不过,基于与上一节同样的逻辑,推荐使用下面的设置:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.20.0</version>
    <scope>runtime</scope>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

配置文件(log4j2.xml)

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO"> <!-- log4j internal log level -->
    <Appenders>
        <Console name="CONSOLE" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>

        <RollingFile name="ROLLING_FILE"
                     fileName="logs/log4j2/roll-by-time-and-size/app.log"
                     filePattern="logs/log4j2/roll-by-time-and-size/app-%d{yyyy-MM-dd-HH}-%i.log"
                     ignoreExceptions="false">
            <PatternLayout>
                <Pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</Pattern>
            </PatternLayout>
            <Policies>
                <!-- 启动时,会删除多余的日志文件 -->
                <OnStartupTriggeringPolicy/>
                <!-- 自动感知filePattern中的时间设置,本例中是按小时粒度进行滚动 -->
                <TimeBasedTriggeringPolicy/>
                <!-- 单个日志文件超过10M,则进行滚动,递增编号(即filePattern中的%i) -->
                <SizeBasedTriggeringPolicy size="10M"/>
            </Policies>
            <!-- max配置与上面的filePattern结合,由于本例中是按小时粒度进行滚动,因此这里表示每小时内最多产生五个编号文件,超出这循环覆盖,如不设置max,则默认为7 -->
            <DefaultRolloverStrategy max="5">
                <Delete basePath="logs" maxDepth="1">
                    <!-- 最近30天,最多5GB的日志 -->
                    <IfFileName glob="app-*.log">
                        <IfAny>
                            <IfLastModified age="30d"/>
                            <IfAccumulatedFileSize exceeds="5GB"/>
                        </IfAny>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

    </Appenders>
    <Loggers>
        <Root level="warn">
            <AppenderRef ref="CONSOLE"/>
            <AppenderRef ref="ROLLING_FILE"/>
        </Root>
    </Loggers>
</Configuration>

代码示例

由于与上例相同,都是基于SLF4J接口层,因此使用方式相同:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
private static final Logger logger = LoggerFactory.getLogger(App.class);
...
logger.info("First name: {}, last name: {}", firstName, lastName);




单独使用Log4j

一般我们会基于SLF4J接口层进行开发,但是如果你硬要单独使用Log4j,也不是不可以。

依赖配置

最简单的,我们可以使用以下配置:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.20.0</version>
</dependency>

不过,由于Log4j自身也分了接口层和实现层,推荐使用如下配置:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.20.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.20.0</version>
    <scope>runtime</scope>
    <exclusions>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

配置文件(log4j2.xml)

(同上)

代码示例

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
...
private static final Logger logger = LogManager.getLogger(App.class);
...
logger.info("First name: {}, last name: {}", firstName, lastName);

有人可能会说,Log4j自身拆成了接口层和实现层,是不是意味着,使用Log4j接口层的情况下,实现层还能使用别的日志系统?是的,例如可以使用「Log4j接口层 + Logback」的搭配:

<!-- 1) Log4j接口层 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.20.0</version>
</dependency>
<!-- 2) Log4j项目提供的「桥接层」,将Log4j接口层桥接到SLF4J接口层,由于Logback是基于SLF4J,因此经过桥接之后,就可以使用Logback作为实现层 -->
<!-- 注:log4j-to-slf4j含有对log4j-api的依赖,因此上面可以不用单独列出log4j-api依赖,不过,为了逻辑清晰,还是保留 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>2.20.0</version>
</dependency>
<!-- 3) Logback实现层 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.12</version>
    <scope>runtime</scope>
</dependency>



总结

如果你在开发一个玩具项目,对于日志框架的选择和使用当然可以比较随意,但是,如果是开发一个正经的项目,尤其是你的项目将作为公众可用的第三方库时,遵循最佳实践、保持灵活性则是非常必要的,因为你不知道使用方希望在他的项目中使用什么日志框架。

另外,我曾经作为面试官的时候,也常常询问面试者如何配置日志框架的依赖,这是一个很简单的题目,不过,一样可以考察对方几个知识点,包括日志框架、解耦、Maven中的scope设置等,总之,这是一个不错的考察编程常识的点。



标签:Java,slf4j,Log4j,SLF4J,logging,org,日志,Logback,log4j
From: https://www.cnblogs.com/morvenhuang/p/17658961.html

相关文章

  • 一口气看完java并发编程
    JUC进程与线程进程程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程......
  • Java8 新特性全面介绍,强烈建议收藏
    阅读大约10分钟,实用性非常高,建议收藏PC阅读移步文末前言Java8已经公布有一段时间了,种种迹象表明Java8是一个有重大改变的发行版。在JavaCodeGeeks上已经有很多介绍Java8新特性的文章,例如PlayingwithJava8–LambdasandConcurrency、Java8DateTimeAPITu......
  • Java RMI实现RPC(远程过程调用)
    RMI(RemoteMethodInvocation,远程方法调用)是一个JavaRPC的API,用于一台主机传递参数并远程调用另一台主机上的方法,下面给出一个简单实例。环境:win10宿主机作为rmiclient,ubuntu虚拟机(IP为192.168.129.49)作为rmiserver。现在client希望向server传递参数,调用server端的服务并获取......
  • LeetCode-26. 删除有序数组中的重复项(Java)
    这是我在51CTO博客开启的写作之路,第一次正式写博客记录我在LeetCode的刷题日,希望能帮助更多的小伙伴攻面自己心仪的公司offer。如下对于 LeetCode-26. 删除有序数组中的重复项,进行全面解析并小结解题思路,同学们请参考:1.题目描述给你一个 升序排列 的数组 nums ,请你 原地 删......
  • java最容易犯错的8道面试题
    1.static和final的用法static的作用从三个方面来谈,分别是静态变量、静态方法、静态类。静态变量:声明为static的静态变量实质上就是全局变量,当声明一个对象时,并不产生static变量的拷贝,而是该类所有实例变量共用同一个static变量。也就是说这个静态变量只加载一次,只分配一......
  • java最容易犯错的8道面试题
    1.static和final的用法static的作用从三个方面来谈,分别是静态变量、静态方法、静态类。静态变量:声明为static的静态变量实质上就是全局变量,当声明一个对象时,并不产生static变量的拷贝,而是该类所有实例变量共用同一个static变量。也就是说这个静态变量只加载一次,只分配......
  • java高频面试题(反射、对象拷贝)
    java高频面试题(反射、对象拷贝)什么是反射?反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力Java反射:在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法Java反射机制主要提供了以下功能:在运行时判断任意一个......
  • java-文件复制练习
    packagecom.example.ss_0203_array.test.test_0826;importjava.io.*;publicclasstest2{publicstaticvoidmain(String[]args)throwsIOException{Filesrc=newFile("F:\\阿里云盘下载\\B站黑马java基础\\day10_字符串\\代码\\mystring");......
  • Java中static关键字
    叙述:static关键字是很多朋友在编写和阅读代码时比较难理解的一个关键字,但也是面试尤其笔试的考点。下面就从static关键字的用途常见面试题两个方面来描述以下我对static关键字的理解原文链接:http://www.cnblogs.com/dolphin0520/p/3799052.html一.static关键字用途在《......
  • 面试类-Java编程(二)
    18.说一下你对Java内存模型(JMM)的理解?Java内存模型(JavaMemoryModel,JMM),是一种抽象的模型,被定义出来屏蔽各种硬件和操作系统的内存访问差异。JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(MainMemory)中,每个线程都有一个私有的本地内存(LocalMemory),本地内......