SpringBoot默认日志框架(slf4j)的使用以及配置文件
前言
SpringBoot 在所有内部日志中使用Commons Logging,但是默认配置也提供了对常用日志的支持,如:Java Util Logging,Log4J, Log4J2和Logback。每种Logger都可以通过配置使用控制台或者文件输出日志内容。
默认日志Logback
SLF4J——简单日志门面,对应的英文为Simple Logging Facade,是存取日志的标准接口,包括slf4j、log4j、jdk logging api和apache common-log等具体实现。
其实SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现。
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.5)
2021-05-11 14:37:21.934 INFO 10716 --- [ restartedMain] com.pro.MyBlogSiteApplication : Starting MyBlogSiteApplication using Java 11.0.9 on DESKTOP-04S0A5K with PID 10716 (C:\Users\ibbd\Desktop\sundry\my-blog-site\target\classes started by ibbd in C:\Users\ibbd\Desktop\sundry\my-blog-site)
2021-05-11 14:37:21.937 INFO 10716 --- [ restartedMain] com.pro.MyBlogSiteApplication : The following profiles are active: dev
2021-05-11 14:37:22.021 INFO 10716 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2021-05-11 14:37:22.021 INFO 10716 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2021-05-11 14:37:23.584 INFO 10716 --- [ restartedMain] c.a.d.s.b.a.DruidDataSourceAutoConfigure : Init DruidDataSource
2021-05-11 14:37:24.675 INFO 10716 --- [ restartedMain] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2021-05-11 14:37:25.656 INFO 10716 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)
2021-05-11 14:37:25.675 INFO 10716 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
从默认的日志信息可以看出
-
时间日期:精确到毫秒
-
日志级别:ERROR, WARN, INFO, DEBUG or TRACE
-
进程ID
-
分隔符:--- 标识实际日志的开始
-
线程名:方括号括起来(可能会截断控制台输出)
-
Logger名:通常使用源代码的类名
-
日志内容
自定义日志配置
根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:
Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
Log4j2:log4j2-spring.xml, log4j2.xml
JDK (Java Util Logging):logging.properties
Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项(下面会提到)。
默认的命名规则,并且放在 src/main/resources
下面即可
如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,application.yml可以通过logging.config属性指定自定义的名字:
logging.config=classpath:logging-config.xml
日志级别
日志级别从低到高分为
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
如果设置为 WARN
,则低于 WARN
的信息都不会输出
一般设置为 DEBUG
就够用了
使用
1. 依赖
spring-boot-starter
这个包就自带了 logging
的依赖包, AOP这个包后面会用到
使用的 SpringBoot 版本:v2.4.5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 配置 logback-spring.xml
在 src/main/resources
目录下新建 logback-spring.xml
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="30 seconds">
<!-- 彩色日志依赖 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 日志文件储存地址, 通过application配置文件传入 -->
<!-- <springProperty scope="context" name="LOG_PATH" source="logback.logDir" />-->
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_PATH" value="./logs"/>
<!-- 控制台彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<!-- 日志文件日志格式 -->
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%15t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 按照每天及大小生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_PATH}/my-blog-site.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件最大大小-->
<maxFileSize>100MB</maxFileSize>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 过滤级别,
如果想分类生成日志文件的话(分成debug、info、error等三个日志文件, 每个文件只记录自己级别的日志),
1. 直接把这个 <appender> 复制三分改一下 FileNamePattern 和 name.
2. 把 <filter> 注释去掉改一下 level 就可以了
-->
<!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- <level>info</level>-->
<!-- <onMatch>ACCEPT</onMatch>-->
<!-- <onMismatch>DENY</onMismatch>-->
<!-- </filter>-->
</appender>
<!-- 日志输出级别
如果使用springProfile, 就需要在application配置文件中通过 spring.profiles.active=dev 来指定环境,
也可以直接去掉 <springProfile> 这个标签或者把它整个注释掉
-->
<springProfile name="dev,prod">
<logger name="org.springframework" level="INFO" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</logger>
<logger name="com.netflix" level="INFO" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</logger>
<logger name="com.pro.test" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</logger>
<logger name="java.sql" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</logger>
<!--项目包的路径-->
<logger name="com.pro" level="DEBUG,INFO" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</logger>
</springProfile>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
3. 全局日志AOP实现
Pointcut
切入点说明
1. execution(public * *(..)) # ——表示匹配所有public方法
2. execution(* set*(..)) # ——表示所有以“set”开头的方法
3. execution(* com.pro.service.UserService.*(..)) # ——表示匹配所有UserService接口的方法
4. execution(* com.pro.service.*.*(..)) # ——表示匹配service包下所有的方法
5. execution(* com.pro.service..*.*(..)) # ——表示匹配service包和它的子包下的方法
# () 参数说明
()匹配没有参数; (..)代表任意多个参数; (*)代表一个参数,但可以是任意型; (*,String)代表第一个参数为任何值,第二个为String类型。
记录日志
package com.pro.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* 请求日志处理
*/
@Aspect
@Component
public class WebLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
ThreadLocal<Long> startTime = new ThreadLocal<>();
// 切入点
@Pointcut("execution(* com.pro.controller..*.*(..))")
public void pc() {}
// 前置通知
@Before("pc()")
public void doBefore(JoinPoint joinPoint) {
// 记录请求开始时间
startTime.set(System.currentTimeMillis());
// 接收请求, 记录请求内容
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
// 记录请求内容
LOGGER.info("URL: " + request.getRequestURL().toString());
LOGGER.info("HTTP_METHOD: " + request.getMethod());
LOGGER.info("IP: " + request.getRemoteAddr());
LOGGER.info("CLASS_METHOD: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
LOGGER.info("Args: " + Arrays.toString(joinPoint.getArgs()));
}
// 返回通知
@AfterReturning(returning = "result", pointcut = "pc()")
public void doAfterReturning(Object result) {
// 请求返回内容
LOGGER.info("RESPONSE: " + result);
LOGGER.info("SPEND TIME: " + (System.currentTimeMillis() - startTime.get()));
// 用完之后移除, 避免内存泄漏
startTime.remove();
}
// 异常通知
@AfterThrowing(value = "pc()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
// 获取类名加方法名
String name = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
// 记录异常信息
LOGGER.info("Exception_Class_Method: {}, Exception_Message: {}", name, e.getMessage());
}
}