• 一文搞定: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

  • 相关阅读:
    微信小程序之授权 wx.authorize
    微信小程序之可滚动视图容器组件 scroll-view
    纯 CSS 利用 label + input 实现选项卡
    Nuxt.js + koa2 入门
    koa2 入门(1)koa-generator 脚手架和 mongoose 使用
    vue 自定义指令
    时运赋
    WEBGL 2D游戏引擎研发系列 第一章 <新的开始>
    EasyUI特殊情况下的BUG整理
    数字时钟DigClock
  • 原文地址:https://www.cnblogs.com/crazymakercircle/p/16344744.html
Copyright © 2020-2023  润新知