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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章