【JAVA日志框架大全】一文快速讲透JAVA日志体系

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【JAVA日志框架大全】一文快速讲透JAVA日志体系

1.概述

发展史:

java日志体系中是现有的log4j,后面才有了JDK自带的jul,两者是两套体系,互不兼容。后来为了不同日志体系之间的兼容,apache推出了jcl日志门面。后来log4j的作者因为和apache理念不合,离开apache后推出了一个全新的日志实现——logback,以及一个全新的日志门面——slf4j。最后apache优化了log4j推出了log4j2。

日志框架的核心问题:

日志是用来记录应用的一些运行信息的。假设没有日志框架,我们要在应用里手动实现日志相关功能,我们需要关注些什么?其实仔细想想无非两点:

  • 记录哪些信息?
  • 记录到哪里去?

当然作为日志框架来说,为了方便使用,它还要关注一点就是:

  • 如何进行方便的配置

以上三点核心问题,我们看作为日志框架的开山鼻祖的log4j是怎样解决的:

log4j给出的答案是:

  • 记录哪些信息——日志级别(level)
  • 记录到哪里去——提供不同的输出方式(appender),文件、控制台、其它等等
  • 如何进行方便的配置——除硬编码外,提供配置文件

2.jul

jul是JDK自带的日志框架,作者之前有一篇文章专门讲过,可以移步:

【JAVA日志框架】JUL,JDK原生日志框架详解。-CSDN博客

3.log4j

3.1.概述

log4j最后一个版本是12年发布的,如今apache已经停止对其维护,现在的项目基本上是不会选用log4j来作为日志框架了,但是作为第一款日志框架,其很多设计思想和体系架构,均成为了标准,后面的日志框架其实都是效仿的log4j。

依赖:

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

3.2.日志级别

日志级别:

import org.apache.log4j.Logger;
import org.junit.Test;
 
public class test {
    private static Logger logger=Logger.getLogger(test.class);
    @Test
    public void test1(){
        logger.trace("trace");
        logger.debug("debug");
        logger.info("info");
        logger.warn("warn");
        logger.error("error");
        logger.fatal("fatal");
    }
}

3.3.配置

如果直接像上面一样使用,会报错:

原因很简单,log4j没有进行初始化的默认配置,需要手动去进行配置,才能使用。

log4j的配置,主要就是配置appender,以下将展示几个实际工程中常用的appender:


ConsoleAppender


FileAppender


DailyRollingFileAppender,将每天的日志单独写成一个文件,即每天一个日志文件。


1.ConsoleAppender:

#控制台的appender,stdout是自定义的一个appender的名字。

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

#自定义的这个appender用什么方式来输出,这里用System.out

log4j.appender.stdout.Target=System.out

#自定义的这个appender的日志级别

log4j.appender.stdout.Threshold=INFO

#自定义的这个appender的日志的格式

#d:data %-5p:用5个字符来打印日志级别 %20c:用20个字符来打印全类名 %L:第几行 %m:打印自己的message

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %20c %L:%m %n


2.FileAppender:


#文件appender

log4j.appender.a1=org.apache.log4j.FileAppender

#自定义的这个appender用什么方式来输出,这里用System.out

log4j.appender.a1.File=${user.home}/LogDemo/log4j/a1.log

#自定义的这个appender的日志级别

log4j.appender.a1.Threshold=INFO

#自定义的这个appender的日志的格式

#d:data %-5p:用5个字符来打印日志级别 %20c:用20个字符来打印全类名 %L:第几行 %m:打印自己的message

log4j.appender.a1.layout=org.apache.log4j.PatternLayout

log4j.appender.a1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %20c %L:%m %n


3.DailyRollingFileAppender:


#将每天的日志单独写成一个文件,即每天一个日志文件的appender,stdout是自定义的一个appender的名字。

log4j.appender.a2=org.apache.log4j.DailyRollingFileAppender

#自定义的这个appender用什么方式来输出,这里用System.out

log4j.appender.a2.File=${user.home}/LogDemo/log4j/a1.log

#自定义的这个appender的日志级别

log4j.appender.a2.Threshold=INFO

#自定义的这个appender的日志的格式

#d:data %-5p:用5个字符来打印日志级别 %20c:用20个字符来打印全类名 %L:第几行 %m:打印自己的message

log4j.appender.a2.layout=org.apache.log4j.PatternLayout

log4j.appender.a2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %20c %L:%m %n


光是配置好了,还不能用,要把配置的appender关联到根logger上去:


#日志级别以及appender

log4j.rootLogger=Info,stdout,a1,a2


将配置文件放到classpath下面去即可,log4j会去classpath下找配置文件:

再运行就可以看到效果:

4.日志门面

4.1.jcl+log4j

4.1.1.使用

jcl也是apache推出的一个日志门面,jcl可能听起来比较陌生,它的另一个名字大家就会觉得很熟悉了——commons-logging。jcl最后一次更新是14年,后面apache放弃了对jcl的维护,所以jcl和log4j一样都不太会在新项目里面被使用,学jcl主要是理解它的思想。


依赖:

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

jcl默认使用jul进行输出,其支持的日志级别和log4j一样:

package com.eryi;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
 
public class test {
    private static Log log= LogFactory.getLog(test.class);
    @Test
    public void test1(){
        log.trace("trace");
        log.debug("debug");
        log.info("info");
        log.warn("warn");
        log.error("error");
        log.fatal("fatal");
    }
}

要切换成log4j,直接引入log4j的依赖即可:

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

然后记得把log4j的配置文件放到classpath下面。

然后执行效果如下,可以看到是log4j输出的:

4.1.2.原理

其实源码没什么看头,核心就是去获取log的时候,这个log的底层实现用什么?

jcl里面写死了要适配的所有日志框架的logger的类全路径:

private static final String[] classesToDiscover = new String[]{"org.apache.commons.logging.impl.Log4JLogger", "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog"};


去load这些实现类,然后用反射实例化出来作为底层提供能力的内核。获取实现类的时候用了一个简单的模板模式,提供能力的时候用了一个简单的适配器模式,经此而已。

4.2.sl4j+logback

4.2.1.使用

sl4j是目前常用的日志门面,logback是目前常用的日志框架。

依赖:

<dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
</dependencies>
 

slf4j和jcl使用上是相似的,只是工程的叫法不同,jcl里面叫LogFactory,slf4j里面叫LoggerFactory:

package com.eryi;
 
 
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class test {
    private static Logger logger= LoggerFactory.getLogger(test.class);
    @Test
    public void test1(){
        logger.trace("trace");
        logger.debug("debug");
        logger.info("info");
        logger.warn("warn");
        logger.error("error");
    }
}

输出结果:

4.2.2.配置

logback默认会去classpath里面寻找配置文件。

配置文件示例:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
    <contextName>logback</contextName>
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_SAVE_PATH" value="logs"></property>
    <!-- 定义输出日志记录格式 -->
    <property name="DEFAULT_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger] - (%F:%L\\) : %msg%n"/>
 
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>${DEFAULT_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
 
    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="RollingFileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_SAVE_PATH}/console.log</file>
        <encoder>
            <pattern>${DEFAULT_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${LOG_SAVE_PATH}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留单位数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
 
    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="RollingFileWarn" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_SAVE_PATH}/warn.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_SAVE_PATH}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
 
    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="RollingFileError" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_SAVE_PATH}/error.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_SAVE_PATH}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留单位数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
 
    <!--additivity:是否继承root节点,默认是true继承。默认情况下子Logger会继承父Logger的appender,
    也就是说子Logger会在父Logger的appender里输出。
    若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。-->
    <logger name="org.springframework" level="INFO" additivity="false">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="RollingFileInfo"/>
    </logger>
    <logger name="org.springframework.security" level="INFO"></logger>
    <Logger name="org.apache.ibatis.io.DefaultVFS" level="INFO"/>
    <Logger name="org.apache.ibatis.io" level="INFO"/>
    <!-- 设置日志输出级别,从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF-->
    <root level="ALL">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="DEFAULT_FILE"/>
        <appender-ref ref="RollingFileWarn"/>
        <appender-ref ref="RollingFileError"/>
    </root>
</configuration>

5.适配

由于api均不统一,jcl兼容log4j,slf4j兼容logback,当想交叉组合,比如slf4j+log4j时,需要用到适配包,各门面和各实现之间的适配关系如下图所示,用到的时候去查一下即可:


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
2月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
696 31
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
3月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
401 3
|
21天前
|
Java Maven
java项目中jar启动执行日志报错:no main manifest attribute, in /www/wwwroot/snow-server/z-server.jar-jar打包的大小明显小于正常大小如何解决
在Java项目中,启动jar包时遇到“no main manifest attribute”错误,且打包大小明显偏小。常见原因包括:1) Maven配置中跳过主程序打包;2) 缺少Manifest文件或Main-Class属性。解决方案如下:
java项目中jar启动执行日志报错:no main manifest attribute, in /www/wwwroot/snow-server/z-server.jar-jar打包的大小明显小于正常大小如何解决
|
4天前
|
开发框架 运维 监控
Spring Boot中的日志框架选择
在Spring Boot开发中,日志管理至关重要。常见的日志框架有Logback、Log4j2、Java Util Logging和Slf4j。选择合适的日志框架需考虑性能、灵活性、社区支持及集成配置。本文以Logback为例,演示了如何记录不同级别的日志消息,并强调合理配置日志框架对提升系统可靠性和开发效率的重要性。
|
3月前
|
人工智能 Oracle Java
解决 Java 打印日志吞异常堆栈的问题
前几天有同学找我查一个空指针问题,Java 打印日志时,异常堆栈信息被吞了,导致定位不到出问题的地方。
59 2
|
3月前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
163 1
|
3月前
|
SQL XML 监控
SpringBoot框架日志详解
本文详细介绍了日志系统的重要性及其在不同环境下的配置方法。日志用于记录系统运行时的问题,确保服务的可靠性。文章解释了各种日志级别(如 info、warn、error 等)的作用,并介绍了常用的日志框架如 SLF4J 和 Logback。此外,还说明了如何在 SpringBoot 中配置日志输出路径及日志级别,包括控制台输出与文件输出的具体设置方法。通过这些配置,开发者能够更好地管理和调试应用程序。
|
4月前
|
Java
日志框架log4j打印异常堆栈信息携带traceId,方便接口异常排查
日常项目运行日志,异常栈打印是不带traceId,导致排查问题查找异常栈很麻烦。
|
5月前
|
XML Java Maven
log4j 日志的简单使用
这篇文章介绍了Log4j日志框架的基本使用方法,包括在Maven项目中添加依赖、配置`log4j.properties`文件以及在代码中创建和使用Logger对象进行日志记录,但实际打印结果中日志级别没有颜色显示。
log4j 日志的简单使用
|
5月前
|
XML Java Maven
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
这篇文章是Spring5框架的入门到实战教程,介绍了Spring5的新功能——整合日志框架Log4j2,包括Spring5对日志框架的通用封装、如何在项目中引入Log4j2、编写Log4j2的XML配置文件,并通过测试类展示了如何使用Log4j2进行日志记录。
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)