netty4----日志框架的檢查


https://segmentfault.com/a/1190000005797595

 

Netty4.x Internal Logger機制

 

netty-1
Netty是一個簡化Java NIO編程的網絡框架。就像人要吃飯一樣,框架也要打日志。
Netty不像大多數框架,默認支持某一種日志實現。相反,Netty本身實現了一套日志機制,但這套日志機制並不會真正去打日志。相反,Netty自身的日志機制更像一個日志包裝層。

日志框架檢測順序

Netty在啟動的時候,會自動去檢測當前Java進程的classpath下是否已經有其它的日志框架。
檢查的順序是:
netty-2

先檢查是否有slf4j,如果沒有則檢查是否有Log4j,如果上面兩個都沒有,則默認使用JDK自帶的日志框架JDK Logging。
JDK的Logging就不用費事去檢測了,直接拿來用了,因為它是JDK自帶的。

注意到雖然Netty支持Common Logging,但在Netty本文所用的4.10.Final版本的代碼里,沒有去檢測Common Logging,即使有支持Common Logging的代碼存在。

日志框架檢測細節

在Netty自身的代碼里面,如果需要打日志,會通過以下代碼來獲得一個logger,以io.netty.bootstrap.Bootstrap這個類為例,讀者可以翻開這個類瞧一瞧。

private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);

要知道Netty是怎么得到logger的,關鍵就在於這個InternalLoggerFactory類了,可以看出來,所有的logger都是通過這個工廠類產生的。
翻開InternalLoggerFactory類的代碼,可以看到類中有一個靜態初始化塊

    private static volatile InternalLoggerFactory defaultFactory; static { final String name = InternalLoggerFactory.class.getName(); InternalLoggerFactory f; try { f = new Slf4JLoggerFactory(true); f.newInstance(name).debug("Using SLF4J as the default logging framework"); defaultFactory = f; } catch (Throwable t1) { try { f = new Log4JLoggerFactory(); f.newInstance(name).debug("Using Log4J as the default logging framework"); } catch (Throwable t2) { f = new JdkLoggerFactory(); f.newInstance(name).debug("Using java.util.logging as the default logging framework"); } } defaultFactory = f; } 

Javaer們都知道,類的初始化塊會在類第一次被使用的時候執行。那么什么時候稱之為第一次被使用呢?比如說,靜態方法被調用,靜態變量被訪問,或者調用構造函數。
當調用InternalLoggerFactory.getInstance(Bootstrap.class)之前,上面的靜態塊會被調用,而Netty對於當前應用所使用的日志框架的檢測,就是在這短短的20幾行代碼里面實現。

首先從代碼整體上可以看到,一個try-catch,在catch里面又嵌套了一個try-catch,這正好體現了日志框架的檢測順序:先檢測SLF4J,后檢測Log4J,都沒有的話,就直接使用JDK Logging

檢測SLF4J

f = new Slf4JLoggerFactory(true);這里開始檢測SLF4J是否存在。

public class Slf4JLoggerFactory extends InternalLoggerFactory { public Slf4JLoggerFactory() { } Slf4JLoggerFactory(boolean failIfNOP) { assert failIfNOP; // Should be always called with true. // SFL4J writes it error messages to System.err. Capture them so that the user does not see such a message on // the console during automatic detection. final StringBuffer buf = new StringBuffer(); final PrintStream err = System.err; try { System.setErr(new PrintStream(new OutputStream() { @Override public void write(int b) { buf.append((char) b); } }, true, "US-ASCII")); } catch (UnsupportedEncodingException e) { throw new Error(e); } try { if (LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory) { throw new NoClassDefFoundError(buf.toString()); } else { err.print(buf.toString()); err.flush(); } } finally { System.setErr(err); } } @Override public InternalLogger newInstance(String name) { return new Slf4JLogger(LoggerFactory.getLogger(name)); } } 

在這里可以看到Slf4JLoggerFactoryInternalLoggerFactory的一個子類實現。
如果應用的classpath下存在slf4j相關的jar包,那么當slf4j的日志框架初始化的時候,如果產生了什么錯誤,將會通過System.err輸出;
對於Netty來講,即使slf4j初始化失敗,它也不願讓用戶看到錯誤輸出,因為對netty來說,slf4j初始化失敗並不代表netty不能選擇其它日志框架;
所以可以從上面代碼中看到,一開始先把System.err給替換掉,讓err輸出被重定向到一個StringBuffer,如下代碼所示:

        // SFL4J writes it error messages to System.err. Capture them so that the user does not see such a message on // the console during automatic detection. final StringBuffer buf = new StringBuffer(); final PrintStream err = System.err; try { System.setErr(new PrintStream(new OutputStream() { @Override public void write(int b) { buf.append((char) b); } }, true, "US-ASCII")); } catch (UnsupportedEncodingException e) { throw new Error(e); }

我們已經明白上面這段代碼,就是為了重定向err輸出,不讓用戶輕易看到。接下來看這些代碼:

        try { if (LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory) { throw new NoClassDefFoundError(buf.toString()); } else { err.print(buf.toString()); err.flush(); } } finally { System.setErr(err); }

首先可以看到一個try-finally結構,finally塊里把System.err復位了,也就是說在初始化SLF4J之后,無論發生什么事,都應該把System.err復位。
接下來看try塊里面的代碼:

            if (LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory) { throw new NoClassDefFoundError(buf.toString()); } else { err.print(buf.toString()); err.flush(); }

解釋這些代碼之前,我們先要認識到,SLF4J其實是一個日志門面(facade),它可以充當Log4j, Logback等日志框架的包裝器。因此你的應用除了要有slf4j的依賴包,還要有其它具體的日志實現框架的依賴。例如下面是我的maven依賴,依賴了slf4j還有Logback。

 <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>${logback.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> <scope>runtime</scope> </dependency> 

如果只有slf4j,而沒有logback,那么LoggerFactory.getILoggerFactory() instanceof NOPLoggerFactory就會為true,然后代碼就會拋出NoClassDefFoundError
如果連slf4j本身都沒有呢?那么運行到LoggerFactory.getLoggerFactory()就已經拋出異常了,因為找不到這個LoggerFactory類。
以上便是檢測SLF4J的整個過程。

檢測Log4J

如果需要檢測Log4J,則說明檢測不到SLF4J的存在,或者是SLF4J不可以使用。
回到InternalLoggerFactory代碼里:

        try { f = new Slf4JLoggerFactory(true); f.newInstance(name).debug("Using SLF4J as the default logging framework"); defaultFactory = f; } catch (Throwable t1) { try { f = new Log4JLoggerFactory(); f.newInstance(name).debug("Using Log4J as the default logging framework"); } catch (Throwable t2) { f = new JdkLoggerFactory(); f.newInstance(name).debug("Using java.util.logging as the default logging framework"); } } 

Log4J的檢測很簡單,很直接,直接在newInstance()方法里加載org.apache.log4j.Logger;類,如果加載不到,直接拋異常,然后轉而直接使用JDK Logging。

public class Log4JLoggerFactory extends InternalLoggerFactory { @Override public InternalLogger newInstance(String name) { return new Log4JLogger(Logger.getLogger(name)); } } 

兼容性

日志級別

Netty的內部日志機制也自定義了日志打印級別,像日志的layout或者appender,則沒有自己定義,完全交給底層的日志框架去做。

public enum InternalLogLevel { /** * 'TRACE' log level. */ TRACE, /** * 'DEBUG' log level. */ DEBUG, /** * 'INFO' log level. */ INFO, /** * 'WARN' log level. */ WARN, /** * 'ERROR' log level. */ ERROR } 

這里會面臨日志打印級別的兼容性問題,因為SLF4J,Log4J,以及JDK Logging,都有自己的日志打印級別,比如說JDK Logging,它的日志打印級別是這樣的:

  • SEVERE (highest value)

  • WARNING

  • INFO

  • CONFIG

  • FINE

  • FINER

  • FINEST (lowest value)

不僅數目對不上,而且名稱也沒對上。Netty采用的方式是,按級別的高低來匹配,比如Netty的DEBUG將會對應到JDK的FINE,以此做到級別的對應關系和兼容性。

消息格式化

SLF4J的Logger會有這樣一種打日志的方式,采用占位的方式,舉個例子:、

logger.info("Hello, I m {}, I m the president of {}","Obama","America");

上面這行代碼的日志輸出結果是:

Hello, I m Obama, I m the president of America

可以看到,{}大括號是一個占位符,其內容將會被后面的參數所代替。
但Log4J和JDK Logging並不支持這種占位的日志打印方式,因此Netty又自己搞了一下,讓它的InternalLogger可以以占位的方式格式化日志輸出信息。
詳情可以參考io.netty.util.internal.logging.MessageFormatter這個類,到這里就不再展開了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM