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


    文章很长,建议收藏起来慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 :


    推荐:入大厂 、做架构、大力提升Java 内功 的 精彩博文

    入大厂 、做架构、大力提升Java 内功 必备的精彩博文 秋招涨薪1W + 必备的精彩博文
    1:Redis 分布式锁 (图解-秒懂-史上最全) 2:Zookeeper 分布式锁 (图解-秒懂-史上最全)
    3: Redis与MySQL双写一致性如何保证? (面试必备) 4: 面试必备:秒杀超卖 解决方案 (史上最全)
    5:面试必备之:Reactor模式 6: 10分钟看懂, Java NIO 底层原理
    7:TCP/IP(图解+秒懂+史上最全) 8:Feign原理 (图解)
    9:DNS图解(秒懂 + 史上最全 + 高薪必备) 10:CDN图解(秒懂 + 史上最全 + 高薪必备)
    11: 分布式事务( 图解 + 史上最全 + 吐血推荐 ) 12:限流:计数器、漏桶、令牌桶
    三大算法的原理与实战(图解+史上最全)
    13:架构必看:12306抢票系统亿级流量架构
    (图解+秒懂+史上最全)
    14:seata AT模式实战(图解+秒懂+史上最全)
    15:seata 源码解读(图解+秒懂+史上最全) 16:seata TCC模式实战(图解+秒懂+史上最全)

    SpringCloud 微服务 精彩博文
    nacos 实战(史上最全) sentinel (史上最全+入门教程)
    SpringCloud gateway (史上最全) 分库分表sharding-jdbc底层原理与实操(史上最全,5W字长文,吐血推荐)

    推荐:尼恩Java面试宝典(持续更新 + 史上最全 + 面试必备)具体详情,请点击此链接

    尼恩Java面试宝典,32个最新pdf,含2000多页不断更新、持续迭代 具体详情,请点击此链接

    在这里插入图片描述


    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,是对不同日志框架提供的一个门面封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。

    日志实现

    实现日志的功能

    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的工厂类,用户只需按照它提供的统一纪录日志接口,最终日志的格式、纪录级别、输出方式等可通过具体日志系统的配置来实现,因此可以灵活的切换日志系统。

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

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

    应用中不可直接使用日志系统(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
    

    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
    

    参考文献

    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

  • 相关阅读:
    openldap
    Java实现 洛谷 P1200 [USACO1.1]你的飞碟在这儿Your Ride Is He…
    Java实现 洛谷 P1200 [USACO1.1]你的飞碟在这儿Your Ride Is He…
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P1567 统计天数
    Java实现 洛谷 P1567 统计天数
  • 原文地址:https://www.cnblogs.com/crazymakercircle/p/16344744.html
Copyright © 2020-2023  润新知