SpringBoot日志系统
SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现。
logback主要包含三个组成部分:Loggers(日志记录器)、Appenders(输出目的在)、Layouts(日志输出格式)
logback 主要分为三个模块,分别是:
- logback-core:提供基础功能,是其他两个模块的基础
- logback-classic : log4j的升级,实现了self4j api
- logback-access:用于与sevlet容器进行集成、提供网络访问日志的功能
logback加载filepath配置文件顺序:
logback.groovy、logback-test.xml、logback.xml
添加依赖
spring-boot-starter中已经存在slf4j依赖,不需要额外添加
配置文件
配置logback.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="FILE_ERROR_PATTERN"
value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} %file:%line: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高, 所以我们使用下面的策略,可以避免输出 Error 的日志-->
<!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- <!–过滤 Error–>-->
<!-- <level>ERROR</level>-->
<!-- <!–匹配到就禁止–>-->
<!-- <onMatch>DENY</onMatch>-->
<!-- <!–没有匹配到就允许–>-->
<!-- <onMismatch>ACCEPT</onMismatch>-->
<!-- </filter>-->
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
<!--<File>logs/info.spring-boot-demo-logback.log</File>-->
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>spring-boot-demo-logging/logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
<!--只保留最近90天的日志-->
<maxHistory>90</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小,默认值是10MB -->
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!--<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
<!--<maxFileSize>1KB</maxFileSize>-->
<!--</triggeringPolicy>-->
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<!-- <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
<!-- <!–如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter–>-->
<!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">-->
<!-- <level>Error</level>-->
<!-- </filter>-->
<!-- <!–日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。–>-->
<!-- <!–<File>logs/error.spring-boot-demo-logback.log</File>–>-->
<!-- <!–滚动策略,按照时间滚动 TimeBasedRollingPolicy–>-->
<!-- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">-->
<!-- <!–文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间–>-->
<!-- <FileNamePattern>logs/spring-boot-demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>-->
<!-- <!–只保留最近90天的日志–>-->
<!-- <maxHistory>90</maxHistory>-->
<!-- <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">-->
<!-- <!– maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 –>-->
<!-- <maxFileSize>2MB</maxFileSize>-->
<!-- </timeBasedFileNamingAndTriggeringPolicy>-->
<!-- </rollingPolicy>-->
<!-- <encoder>-->
<!-- <pattern>${FILE_ERROR_PATTERN}</pattern>-->
<!-- <charset>UTF-8</charset> <!– 此处设置字符集 –>-->
<!-- </encoder>-->
<!-- </appender>-->
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_INFO"/>
<!-- <appender-ref ref="FILE_ERROR"/>-->
</root>
</configuration>
添加AOP日志
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
添加AOP代码
package online.yuluo.springbootdemo.aspectj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
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.Map;
import java.util.Objects;
/**
* @author yuluo
*/
@Component
@Aspect
@Slf4j
public class AopLog {
private static final String START_TIME = "request-start";
@Pointcut("execution(public * online.yuluo.springbootdemo.controller.*Controller.*(..))")
public void log() {
}
@Before("log()")
public void beforeLog(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
log.info("<<<<<<<请求URL:{}", request.getRequestURL());
log.info("<<<<<<<请求IP:{}", request.getRemoteAddr());
log.info("<<<<<<<请求开始:{}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
Map<String, String[]> parameterMap = request.getParameterMap();
log.info("<<<<<<<请求参数:{}", JSON.toJSONString(parameterMap));
Long start = System.currentTimeMillis();
request.setAttribute(START_TIME, start);
}
@Around("log()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
log.info("<<<<<<<返回值:{}", JSON.toJSONString(result));
return result;
}
/**
* 后置操作
*/
@AfterReturning("log()")
public void afterReturning() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
Long start = (Long) request.getAttribute(START_TIME);
Long end = System.currentTimeMillis();
log.info("<<<<<<<请求耗时:{}毫秒", end - start);
}
}
添加sql执行日志
本次引入了p6spy
添加依赖
<!-- Spring Aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.github.gavlyukovskiy</groupId>
<artifactId>p6spy-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
修改application配置文件
url driver-class-name
spring:
datasource:
url: jdbc:p6spy:mysql://eurekaserver01.com:2333/demo?useSSL=false&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&useSSL=false # 数据库URL
username: demo # 数据库用户名
password: EanD5NhsdCWLs6zs # 数据库密码
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
添加spy.properties文件
#mybatis 3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#mybatis 3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
#appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
driverlist=com.mysql.cj.jdbc.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
AOP打印详细信息
@Component
@Slf4j
@Aspect
public class SqlAspectj {
@Pointcut("execution(public * online.yuluo.springbootdemo.mapper.*Mapper.*(..))")
public void log() {
}
@Around("log()")
public Object sqlLogAround(ProceedingJoinPoint joinPoint) throws Throwable {
String name = "-";
String result = "Y";
long start = System.currentTimeMillis();
try {
name = joinPoint.getSignature().toShortString();
return joinPoint.proceed();
} catch (Throwable throwable) {
result = "N";
throw throwable;
} finally {
// 执行方法名称,执行时间,执行是否成功
long end = System.currentTimeMillis();
log.info("name:[{}];result:[{}];time:[{}ms]", name, result, end - start);
}
}
// 返回值
@AfterReturning(pointcut = "log()", returning = "returnValue")
public void afterReturning(JoinPoint joinPoint, Object returnValue) {
log.info("return value is " + returnValue);
}
}