文章很長,建議收藏起來慢慢讀!瘋狂創客圈總目錄 語雀版 | 總目錄 碼雲版| 總目錄 博客園版 爲您奉上珍貴的學習資源 :
-
免費贈送 :《尼恩Java面試寶典》持續更新+ 史上最全 + 面試必備 2000頁+ 面試必備 + 大廠必備 +漲薪必備
-
免費贈送 經典圖書:《Java高併發核心編程(卷1)》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《Java高併發核心編程(卷2)》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《Netty Zookeeper Redis 高併發實戰》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《SpringCloud Nginx高併發核心編程》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取
推薦:入大廠 、做架構、大力提升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作爲日誌框架輸出,結構圖如下:
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)結果輸出:
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能自動適配所有的日誌,,引入其他框架的時候,只需要把這個框架依賴的日誌框架排除掉即可
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 解析日誌配置,可以使用:
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
日誌輸出的格式
#在控制檯輸出日誌的格式
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
日誌輸出格式:
%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