日志门面 (日志抽象) | 日志实现 |
---|---|
JCL(Jakarta Commons Logging) SLF4J(Simple Logging Facade for Java) | Jul(Java Util Logging) , Log4j , Log4j2 , Logback |
记录型日志框架
门面型日志框架
选择日志框架的方式,就是先选择一个门面(抽象层) ,然后再选择一个实现
Spring默认使用JCL
SpringBoot默认选用的是SLF4J和 Logback.
Slf4j的设计思想比较简洁,使用了Facade设计模式,Slf4j本身只提供了一个slf4j-api-version.jar包,这个jar中主要是日志的抽象接口,jar中本身并没有对抽象出来的接口做实现。
对于不同的日志实现方案(例如Logback,Log4j…),封装出不同的桥接组件(例如logback-classic-version.jar,slf4j-log4j12-version.jar),这样使用过程中可以灵活的选取自己项目里的日志实现。
日志方法的调用,不要直接调用日志的实现类, 而是调用日志抽象层里的方法 (面向抽象编程).
阿里的开发手册上有一条关于日志的规范:
日志框架之间的关系
代码示例
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
配置文件
SLF4j与其它日志组件集成
应用调了sl4j-api,日志门面接口。日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用。由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,上图红框中的组件即是对应的各种桥接器!
在SpringBoot中,底层是Spring框架,Spring框架默认使用JCL,而****SpringBoot默认集成的日志框架使用的是SLF4j+Logback组合。
**因为 **spring-boot-starter-logging
是Logback的日志实现,而Spring Boot启动项spring-boot-starter又依赖了spring-boot-starter-logging,所以Spring Boot就默认集成了Logback。
SpringBoot默认集成了Logback,可以开箱即用,非常方便。在基于SpringBoot实现的系统中,使用SLF4j方法如下:
日志记录方法的调用,不应该来直接调用日志的实现类,而是应该调用日志抽象层的方法,即直接使用SLF4j日志门面。
在代码中使用的方式如下:
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class LogDemo {
private final Logger logger = LoggerFactory.getLogger(LogDemo.class);
@Test
public void testLog(){
Logger logger = LoggerFactory.getLogger(LogDemo.class);
logger.info("Hello LogBack!");
}
}
//打印结果
15:16:26.759 [main] INFO c.m.h.log.LogDemo - [main,15] - Hello LOG !
在resources文件夹下创建 logback.xml
,logback会自动在该目录下加载该配置文件,在该文件中对打印日志的级别、形式、保存路径等进行配置。
https://www.springcloud.cc/spring-boot.html#boot-features-logging-format
如果可能,我们建议您使用
-spring
变体进行日志记录配置(例如,logback-spring.xml
而不是logback.xml
)。如果使用标准配置位置,Spring无法完全控制日志初始化。
<configuration>
<property name="log.path" value="/home/hejiayun/logs" />
<property name="log.pattern"
value="%red(%date{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %red([%thread]) %boldMagenta(%logger{50}) - [%method,%line] - %cyan(%msg%n)"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}pattern>
encoder>
appender>
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.logfileNamePattern>
<maxHistory>60maxHistory>
rollingPolicy>
<encoder>
<pattern>${log.pattern}pattern>
encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFOlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.logfileNamePattern>
<maxHistory>60maxHistory>
rollingPolicy>
<encoder>
<pattern>${log.pattern}pattern>
encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERRORlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.logfileNamePattern>
<maxHistory>60maxHistory>
rollingPolicy>
<encoder>
<pattern>${log.pattern}pattern>
encoder>
appender>
<logger name="com.msb" level="info" />
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
root>
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
root>
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
logger>
configuration>
configuration 根节点包含的属性:
的子节点有contextName、property、appender、logger、root等,其中contextName和property是属性节点,appender、root、logger是
property标签可用于自定义属性,比如定义一个property name=“log.pattern”,然后使用${log.pattern}去引用它。
日志输出格式说明
%d表示时间
%thread表示线程名
%-5level 表示日志级别,允许以五个字符长度输出
%logger{20}表示具体的日志输出者,比如类名,括号内表示长度
%method:表示方法名字
%line:表示第几行
%msg表示具体的日志消息,就是logger.info("xxx")中的xxx
%n表示换行
是负责写日志的组件,在这里可以理解为一个日志的渲染器,比如console日志选择器、文件渲染器。有两个必要属性name和class:
appender日志输出方式实现类:ConsoleAppender、FileAppender、RollingFileAppender、SocketAppender、SMTPAppender、DBAppender、SyslogAppender、SiftingAppender等.
平时主要使用的是 ConsoleAppender
和 RollingFileAppender
,其中 ConsoleAppender
是往控制台打印日志,RollingFileAppender
是往磁盘文件追加日志,而且可以按照一定的设置方式动态分割日志
1) 控制台输出–ConsoleAppender
${log.pattern}
encoder表示输出格式
2) 文件输入RollingFileAppender
文件输出主要包括配置:以指定格式将日志输出到指定文件夹下的文件中,可以配置该文件的名称、最大大小、保存时间等。
${log.path}/sys-info.log
${log.path}/sys-info.%d{yyyy-MM-dd}.log
60
${log.pattern}
INFO
ACCEPT
DENY
:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名(重命名文件)。
的作用是当发生滚动时,定义RollingFileAppender的行为,其中上面的TimeBasedRollingPolicy是最常用的滚动策略,它根据时间指定滚动策略,既负责滚动也负责触发滚动,有以下节点:
,必要节点,包含文件名及"%d"转换符,"%d"可以包含一个Java.text.SimpleDateFormat指定的时间格式,如%d{yyyy-MM},如果直接使用%d那么格式为yyyy-MM-dd。RollingFileAppender的file子节点可有可无,通过设置file可以为活动文件和归档文件指定不同的位置。
,可选节点,控制保留的归档文件的最大数量,如果超出数量就删除旧文件,假设设置每个月滚动且
是6,则只保存最近6个月的文件。
logbcak允许给日志记录器appender配置一个或多个Filter(或者给整体配置一个或多个TurboFilter),来控制:当满足过滤器指定的条件时,才记录日志(或不满足条件时,拒绝记录日志) 只记录ERROR级别的日志,其他级别的日志拒绝记录
ERROR
ACCEPT
DENY
用来设置某一个包或者具体某一个类的日志打印级别、以及指定
。
可以包含零个或者多个
元素,标识这个appender将会添加到这个logger。
仅有一个name属性、一个可选的level属性和一个可选的additivity属性:
root节点实际上是配置启用哪种appender,可以添加多个appender。
也是
元素,但是它是根logger,只有一个level属性,因为它的name就是ROOT。 如果没有指定logger,则那么所有的logger都会继承根logger的level。
appender-ref : 表示level为info级别,启用渲染器`CONSOLE
TRACE < DEBUG < INFO < WARN < ERROR
详解:
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class LogDemo {
private final Logger logger = LoggerFactory.getLogger(LogDemo.class);
/**
* 传统方式实现日志
*/
@Test
public void test1(){
logger.error("发生了严重错误,程序阻断了,需要立即处理,发送警报");
logger.warn("这个错误很少见,不影响程序继续运行,酌情处理");
logger.info("没有什么问题,单纯想打印个日志");
logger.debug("你经常写BUG,测试的时候,多打点日志");
logger.trace("这个级别很少用,为了追踪");
}
/**
* Slf4j注解方式实现日志
* 每次写新的类,就需要重新写logger,麻烦,可以使用@Slf4j注解简化:
*/
@Test
public void test2(){
log.error("发生了严重错误,程序阻断了,需要立即处理,发送警报");
log.warn("这个错误很少见,不影响程序继续运行,酌情处理");
log.info("没有什么问题,单纯想打印个日志");
log.debug("你经常写BUG,测试的时候,多打点日志");
log.trace("这个级别很少用,为了追踪");
}
}
日志打印的规范介绍
@Test
public void logNorm(){
//使用{}作为占位符,而不是字符串拼接
String name = "我是大佬";
log.info("hello {}",name);
log.debug("hello " + name);
String userId = "10010";
String orderId = "3242343253253535";
log.debug("order is paying with userId:[{}] and orderId : [{}]",userId, orderId);
// e.printStackTrace();不使用这种,打印堆栈日志与业务日志混合
try {
int i = 1 / 0;
} catch (Exception e) {
// e.printStackTrace();
log.error("/ by zero",e);
}
//先拼接字符串“hello”和“name”。然后执行debug方法,判断日志级别。
log.debug("hello" + name);
//不提前拼接,先判断日志级别, 然后选择是否执行debug方法,拼接字符串“hello”和“world”
//isDebugEnabled() 可以避免无用的字符串操作,提高性能
if(log.isDebugEnabled()){
log.debug("hello" + name);
}
}
日志打印的注意事项