一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)
文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :
免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《尼恩Java面试宝典 最新版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
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
https://blog.csdn.net/qq_32785495/article/details/118964738
https://blog.csdn.net/Lemon_MY/article/details/107220008