一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)

文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :

免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:尼恩Java面试宝典 最新版 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取


Java的日志系统

java领域存在多种日志框架,目前常用的日志框架包括Log4j,Log4j 2,Commons Logging,Slf4j,Logback,Jul。

这些框架中可以分为两类,一类是日志框架,一类是日志实现。

日志框架

门面型日志框架:不实现日志功能,仅整合日志

1)JCL:一套Apache基金所述的java日志接口,由Jakarta Commons Logging,更名为Commons Logging;

Commons Logging:apache提供的一个通用的日志接口。用户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的logging, common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。

2)SIF4J:一套简易的Java日志门面,全称为Simple Logging Facade for Java。

SLF4j:类似于Apache Common-Logging,是对不同日志框架提供的一个门面封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

日志实现

实现日志的功能

1)JUL:JDK中的日志记录工具,自Java1.4来由官方日志实现;

JUL(java.util.logging):JDK提供的日志系统。较混乱,不经常使用

2)Log4j:具体的日志实现框架;

Log4j:经典的一种日志解决方式。内部把日志系统抽象封装成Logger 、appender 、pattern 等实现。

我们能够通过配置文件轻松的实现日志系统的管理和多样化配置。

3)Log4j2:具体日志实现框架;

4)Logback:一个具体的日志实现框架。

Logback:Log4j的替代产品。须要配合日志框架SLF4j使用

日志框架的发展演变

1、Log4j

Gülcü于2001年发布了Log4j框架,也就是后来Apache基金会的顶级项目。

在JDK1.3版本及以前,Java日志的实现依赖于System.out.print()、System.err.println()或者e.printStackTrace()、

Debug日志被写到STDOUT流,

错误日志被写到STDERR流。

这样的日志系统无法定制且粒度太粗,无法精确定位错误。

Log4j定义的Logger、Appender、Level等概念如今已经被广泛使用。

Log4j 的短板在于性能,在Logback和 Log4j2出来之后,Log4j的使用也减少了,目前已停止更新。

2、JUL

受Logj启发,Sun在Java1.4版本中引入了java.util.logging,

但是jull功能远不如log4j完善,开发者需要自己编写Appenders(Sun称之为Handlers),

且只有两个Handlers可用(Console和File),jul在Java1.5以后性能和可用性才有所提升。

3、JCL

JCL(commons-logging)是一个门面框架,它由于项目的日志打印必然选择两个框架中至少一个,

JCL只提供 Log API,不提供实现,实现采用Log4j或者 JUL 。

4、SLF4j

SLF4J(Simple Logging Facade for Java)和 Logback 也是Gülcü创立的项目,目的是为了提供更高性能的实现。

从设计模式的角度说,SLF4J是用来在log和代码层之间起到门面作用,类似于 JCL的Log Facade。

对于用户来说只要使用SLF4J提供的接口,即可隐藏日志的具体实现,

SLF4J提供的核心API是一些接口和一个LoggerFactory的工厂类,用户只需按照它提供的统一纪录日志接口,最终日志的格式、纪录级别、输出方式等可通过具体日志系统的配置来实现,因此可以灵活的切换日志系统。

说明:本文会持续更新,更多最新尼恩3高笔记PDF,请从下面的链接获取:语雀 或者 码云

日志门面框架整合日志实现框架

在阿里开发手册上有关于日志门面使用系统的强制规约:

应用中不可直接使用日志系统(log4j、logback)中的 API ,而应依赖使用日志框架中的 API 。

使用门面模式的日志框架,有利于维护和各个类的日志处理方式的统一。

slf4j-api.jar日志系统(门面框架+桥接器)

由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,

说白了,所谓“桥接器”,不过就是对某套API的伪实现。

“桥接器”:日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用。

“桥接器”实现并不是直接去完成API所声明的功能,而是去调用有类似功能的别的API。这样就完成了从“某套API”到“别的API”的转调。

在这里插入图片描述

“桥接器” 类似于 适配层,

有的时候,这里的“桥接器”也叫适配层

仅使用slf4j-api门面框架

此时没有日志系统的具体实现,所以会报错

使用slf4j-nop空实现

slf4j-nop不会输出任何日志,仅是让slf4j-api.jar不再报错。

Sif4j门面框架+Log4j实现

若项目采用Slf4j门面以Log4j作为日志框架输出,结构图如下:

img

1)添加slf4j的核心依赖:

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

2)Sif4j门面框架+Log4j实现使用的桥接器:

添加桥接依赖:

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

3)测试代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4jSif4jTest {
public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Log4jSif4jTest.class);
        logger.info(logger.getClass().getName());
        logger.info("门面框架Sif4j整合Log4j输出");
}
}

4)结果输出:

img

Sif4j门面框架+log4j 2.x实现

在这里插入图片描述

这里的桥接器(适配层log4j-slf4j-impl.jar

仅需依赖org.apache.logging.log4j:log4j-slf4j-impl:2.12.1,就可以引入所有依赖。

Sif4j门面框架+logback实现

logback一定会依赖slf4j的接口,

所以使用logback的时候,一定使用了slf4j-api.jar的接口。

仅需添加ch.qos.logback:logback-classic:1.2.3即可引入所有依赖的jar包。

SpringBoot的日志

SpringBoot 默认使用info级别日志。日志级别由低到高:trace<debug<info<warn<error

SpringBoot 底层使用slf4j+logback 方式。最底层依赖关系(如下图)导入了slf4j日志抽象层,slf4j-api。使用slf4j+logback的方式进行日志记录。

SpringBoot能自动适配所有的日志,,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可

img

SpringBoot 也把其他日志替换成的 slf4j。给类路径下放置每个日志框架自己的配置文件后,SpringBoot 就不使用其他的默认配置了。

Logging System Customization
Logback logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
Log4j2 log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging) logging.properties

logback-spring.xml:SpringBoot 解析日志配置,可以使用:SpringBoot 配置信息。

logback.xml:直接被日志框架识别了。

SpringBoot记录日志

SpringBoot已经帮我们配置好了日志

Logger logger = LoggerFactory.getLogger(getClass());
 
    @Test
    void contextLoads() {
        logger.trace("trace日志输出......");
        logger.debug("debug日志输出......");
        logger.info("info日志输出......");
        logger.warn("warn日志输出......");
        logger.error("error日志输出......");
    }

Springboot默认使用的info级别的,没有指定级别就用默认的级别(root级别)

logger.info("info日志输出......");
logger.warn("warn日志输出......");
logger.error("error日志输出......");

定日志级别方式在配置文件中配置

#调整日志的输出级别
logging.level.com=trace

指定日志输出的文件和路径

#不指定路径的情况下将日志打印在当前项目下,也可以带着文件的全路径
logging.file.name=E:/springboot.log
#指定日志文件路径
logging.file.path=/springboot/spring.log

img

日志输出的格式

#在控制台输出日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
#在日志文件中输出的日志格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n

img

img

    日志输出格式:
		%d表示日期时间,
		%thread表示线程名,
		%-5level:级别从左显示5个字符宽度
		%logger{50} 表示logger名字最长50个字符,否则按照句点分割。 
		%msg:日志消息,
		%n是换行符
    -->
    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

Netty的封装

由于Java提供的日志框架较多,为了便于使用,Netty封装了一套通用的日志系统。

主要思路是实现了InternalLogger和InternalLoggerFactory,将Logger和LogFactory抽象出来,

netty默认的InternalLoggerFactory会自己查找当前引入的日志框架,然后使用Factory创建Logger实例。

InternalLogger

InternalLogger是一个接口,封装了trace、info、error、debug、warn等方法,用来提供记录日志的方法。

public interface InternalLogger {
String name();
boolean isTraceEnabled();
void trace(String msg);
void trace(String format, Object arg);
void trace(String format, Object argA, Object argB);
void trace(String format, Object... arguments);
void trace(String msg, Throwable t);
void trace(Throwable t);
 ... // 还有debug  info warn error log 
}

AbstractInternalLogger

AbstractInternalLogger是一个抽象日志类,实现了InternalLogger接口中的部分方法,内部包含name变量,

主要实现了log的6个方法,其会在内部会根据InternalLogLevel来调用相应的方法,其他方法在AbstractInternalLogger的子类中实现。

public abstract class AbstractInternalLogger implements InternalLogger, Serializable {
	private final String name;
    
     public boolean isEnabled(InternalLogLevel level) {
        switch (level) {
        case TRACE:
            return isTraceEnabled();
        case DEBUG:
            return isDebugEnabled();
        case INFO:
            return isInfoEnabled();
        case WARN:
            return isWarnEnabled();
        case ERROR:
            return isErrorEnabled();
        default:
            throw new Error();
        }
    }
    
    public void log(InternalLogLevel level, String msg, Throwable cause) {
        switch (level) {
        case TRACE:
            trace(msg, cause);
            break;
        case DEBUG:
            debug(msg, cause);
            break;
        case INFO:
            info(msg, cause);
            break;
        case WARN:
            warn(msg, cause);
            break;
        case ERROR:
            error(msg, cause);
            break;
        default:
            throw new Error();
        }
    }
}

AbstractInternalLogger有5个实现类:

  • CommonsLogger 内部实现了InternalLogger的方法,使用了org.apache.commons.logging.Log logger

  • JdkLogger 内部使用java.util.logging.Logger logger作为实际的日志记录器

  • Log4J2Logger 内部使用org.apache.logging.log4j.Logger logger

  • Log4JLogger 内部使用org.apache.log4j.Logger logger

  • Slf4JLogger 内部使用org.slf4j.Logger logger

    以上这些记录日志类只是内部封装了不同的日志处理的具体框架。

    InternalLogLevel表示日志等级,是一个枚举,TRACE,DEBUG,INFO,WARN,ERROR

InternalLoggerFactory

InternalLoggerFactory是一个抽象的类,其子类有 :

  • CommonsLoggerFactory,

  • JdkLoggerFactory,

  • Log4J2LoggerFactory,

  • Log4JLoggerFactory

  • Slf4JLoggerFactory 。

每个factory需要实现newInstance方法返回InternalLogger实例。

//获取默认的Factory
private static InternalLoggerFactory newDefaultFactory(String name) {
        InternalLoggerFactory f;
        try {
            f = new Slf4JLoggerFactory(true);
            f.newInstance(name).debug("Using SLF4J as the default logging framework");
        } catch (Throwable t1) {
            try {
                f = Log4JLoggerFactory.INSTANCE;
                f.newInstance(name).debug("Using Log4J as the default logging framework");
            } catch (Throwable t2) {
                f = JdkLoggerFactory.INSTANCE;
                f.newInstance(name).debug("Using java.util.logging as the default logging framework");
            }
        }
        return f;
    }

Netty使用logback

1.加入依赖

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback-classic.version}</version>
</dependency>

2.在resources目录加入logback.xml 配置文件

在这里插入图片描述

3.netty代码中添加日志功能,

在这里插入图片描述

并添加注解@Slf4j即可, 输出的日志如下

在这里插入图片描述

Netty中的LoggingHandler

netty自带一个日志记录的Handler,叫LoggingHandler,这个Handler使用netty的日志框架打印日志,而netty默认 的日志是java的日志框架java logger,而java的日志框架默认级别是INFO级别,所以需要我们在pipeline中加入此Handler,则可以打印netty的运行日志。

当在客户端和服务端的ChannelInitializer继承类中添加.addLast(“logging”, new LoggingHandler(LogLevel.INFO))这行代码时

Netty就会以给定的日志级别打印出LoggingHandler中的日志。

可以对入站\出站事件进行日志记录,从而方便我们进行问题排查。

public  class  NettyClientChannelInitializer  extends  ChannelInitializer<SocketChannel>  {

        //给pipeline设置处理器
        protected  void  initChannel(SocketChannel  channel)  throws  Exception  {
                ChannelPipeline  p  =  channel.pipeline();
                p.addLast("logging",new  LoggingHandler(LogLevel.INFO));      //Netty自带的日志记录handler,这个handler使用Netty的日志框架打印日志,可以打印Netty的运行日志
                p.addLast("decoder",  new  StringDecoder(CharsetUtil.UTF_8));      向pipeline加入解码器
                p.addLast("encoder",  new  StringEncoder(CharsetUtil.UTF_8));      向pipeline加入编码器
                //找到管道,添加handler
                p.addLast(new  NettyClientHandler2());
        }
}

假如现在添加这行代码访问http://127.0.0.1:8007/Action?name=1234510

19:10:52.089 [nioEventLoopGroup-2-6] INFO io.netty.handler.logging.LoggingHandler - [id: 0x4a9db561, L:/127.0.0.1:8007 - R:/127.0.0.1:53151] REGISTERED
19:10:52.089 [nioEventLoopGroup-2-6] INFO io.netty.handler.logging.LoggingHandler - [id: 0x4a9db561, L:/127.0.0.1:8007 - R:/127.0.0.1:53151] ACTIVE
19:10:52.090 [nioEventLoopGroup-2-6] DEBUG com.bihang.seaya.server.handler.SeayaHandler - io.netty.handler.codec.http.DefaultHttpRequest
19:10:52.090 [nioEventLoopGroup-2-6] DEBUG com.bihang.seaya.server.handler.SeayaHandler - uri/Action?name=1234510
19:10:52.090 [nioEventLoopGroup-2-6] INFO io.netty.handler.logging.LoggingHandler - [id: 0x4a9db561, L:/127.0.0.1:8007 - R:/127.0.0.1:53151] CLOSE
19:10:52.090 [nioEventLoopGroup-2-6] INFO io.netty.handler.logging.LoggingHandler - [id: 0x4a9db561, L:/127.0.0.1:8007 ! R:/127.0.0.1:53151] INACTIVE
19:10:52.090 [nioEventLoopGroup-2-6] INFO io.netty.handler.logging.LoggingHandler - [id: 0x4a9db561, L:/127.0.0.1:8007 ! R:/127.0.0.1:53151] UNREGISTERED
1234567
public  class  NettyServerChannelInitializer  extends  ChannelInitializer<SocketChannel>  {

        //给pipeline设置处理器
        protected  void  initChannel(SocketChannel  channel)  throws  Exception  {
                ChannelPipeline  p  =  channel.pipeline();
                p.addLast("logging",new  LoggingHandler(LogLevel.INFO));      //Netty自带的日志记录handler,这个handler使用Netty的日志框架打印日志,可以打印Netty的运行日志
                p.addLast("decoder",  new  StringDecoder(CharsetUtil.UTF_8));      向pipeline加入解码器
                p.addLast("encoder",  new  StringEncoder(CharsetUtil.UTF_8));      向pipeline加入编码器
                //找到管道,添加handler
                p.addLast(new  NettyClientHandler2());
        }
}

如果没有这行代码的打印信息

19:15:02.292 [nioEventLoopGroup-2-2] DEBUG com.bihang.seaya.server.handler.SeayaHandler - io.netty.handler.codec.http.DefaultHttpRequest
19:15:02.292 [nioEventLoopGroup-2-2] DEBUG com.bihang.seaya.server.handler.SeayaHandler - uri/Action?name=1234510

说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面的链接获取:语雀 或者 码云

参考文献

https://blog.csdn.net/qq779247257/article/details/97489053
https://www.kancloud.cn/ssj234/netty-source/433218
https://baijiahao.baidu.com/s?id=1699987481329902906&wfr=spider&for=pc
https://blog.csdn.net/qq_32785495/article/details/118964738
https://blog.csdn.net/Lemon_MY/article/details/107220008

posted @ 2022-06-05 19:43  疯狂创客圈  阅读(431)  评论(0编辑  收藏  举报