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文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration>
  3. <property name="FILE_ERROR_PATTERN"
  4. 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}}"/>
  5. <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
  6. <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  7. <filter class="ch.qos.logback.classic.filter.LevelFilter">
  8. <level>INFO</level>
  9. </filter>
  10. <encoder>
  11. <pattern>${CONSOLE_LOG_PATTERN}</pattern>
  12. <charset>UTF-8</charset>
  13. </encoder>
  14. </appender>
  15. <appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
  16. <!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高, 所以我们使用下面的策略,可以避免输出 Error 的日志-->
  17. <!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
  18. <!-- &lt;!&ndash;过滤 Error&ndash;&gt;-->
  19. <!-- <level>ERROR</level>-->
  20. <!-- &lt;!&ndash;匹配到就禁止&ndash;&gt;-->
  21. <!-- <onMatch>DENY</onMatch>-->
  22. <!-- &lt;!&ndash;没有匹配到就允许&ndash;&gt;-->
  23. <!-- <onMismatch>ACCEPT</onMismatch>-->
  24. <!-- </filter>-->
  25. <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
  26. <!--<File>logs/info.spring-boot-demo-logback.log</File>-->
  27. <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
  28. <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
  29. <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
  30. <FileNamePattern>spring-boot-demo-logging/logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
  31. <!--只保留最近90天的日志-->
  32. <maxHistory>90</maxHistory>
  33. <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
  34. <!--<totalSizeCap>1GB</totalSizeCap>-->
  35. <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
  36. <!-- maxFileSize:这是活动文件的大小,默认值是10MB -->
  37. <maxFileSize>2MB</maxFileSize>
  38. </timeBasedFileNamingAndTriggeringPolicy>
  39. </rollingPolicy>
  40. <!--<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
  41. <!--<maxFileSize>1KB</maxFileSize>-->
  42. <!--</triggeringPolicy>-->
  43. <encoder>
  44. <pattern>${FILE_LOG_PATTERN}</pattern>
  45. <charset>UTF-8</charset> <!-- 此处设置字符集 -->
  46. </encoder>
  47. </appender>
  48. <!-- <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
  49. <!-- &lt;!&ndash;如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter&ndash;&gt;-->
  50. <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">-->
  51. <!-- <level>Error</level>-->
  52. <!-- </filter>-->
  53. <!-- &lt;!&ndash;日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。&ndash;&gt;-->
  54. <!-- &lt;!&ndash;<File>logs/error.spring-boot-demo-logback.log</File>&ndash;&gt;-->
  55. <!-- &lt;!&ndash;滚动策略,按照时间滚动 TimeBasedRollingPolicy&ndash;&gt;-->
  56. <!-- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">-->
  57. <!-- &lt;!&ndash;文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间&ndash;&gt;-->
  58. <!-- <FileNamePattern>logs/spring-boot-demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>-->
  59. <!-- &lt;!&ndash;只保留最近90天的日志&ndash;&gt;-->
  60. <!-- <maxHistory>90</maxHistory>-->
  61. <!-- <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">-->
  62. <!-- &lt;!&ndash; maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 &ndash;&gt;-->
  63. <!-- <maxFileSize>2MB</maxFileSize>-->
  64. <!-- </timeBasedFileNamingAndTriggeringPolicy>-->
  65. <!-- </rollingPolicy>-->
  66. <!-- <encoder>-->
  67. <!-- <pattern>${FILE_ERROR_PATTERN}</pattern>-->
  68. <!-- <charset>UTF-8</charset> &lt;!&ndash; 此处设置字符集 &ndash;&gt;-->
  69. <!-- </encoder>-->
  70. <!-- </appender>-->
  71. <root level="info">
  72. <appender-ref ref="CONSOLE"/>
  73. <appender-ref ref="FILE_INFO"/>
  74. <!-- <appender-ref ref="FILE_ERROR"/>-->
  75. </root>
  76. </configuration>

添加AOP日志

添加依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-aop</artifactId>
  4. </dependency>

添加AOP代码

  1. package online.yuluo.springbootdemo.aspectj;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.aspectj.lang.JoinPoint;
  6. import org.aspectj.lang.ProceedingJoinPoint;
  7. import org.aspectj.lang.annotation.*;
  8. import org.springframework.stereotype.Component;
  9. import org.springframework.web.context.request.RequestContextHolder;
  10. import org.springframework.web.context.request.ServletRequestAttributes;
  11. import javax.servlet.http.HttpServletRequest;
  12. import java.util.Map;
  13. import java.util.Objects;
  14. /**
  15. * @author yuluo
  16. */
  17. @Component
  18. @Aspect
  19. @Slf4j
  20. public class AopLog {
  21. private static final String START_TIME = "request-start";
  22. @Pointcut("execution(public * online.yuluo.springbootdemo.controller.*Controller.*(..))")
  23. public void log() {
  24. }
  25. @Before("log()")
  26. public void beforeLog(JoinPoint joinPoint) {
  27. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  28. HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
  29. log.info("<<<<<<<请求URL:{}", request.getRequestURL());
  30. log.info("<<<<<<<请求IP:{}", request.getRemoteAddr());
  31. log.info("<<<<<<<请求开始:{}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
  32. Map<String, String[]> parameterMap = request.getParameterMap();
  33. log.info("<<<<<<<请求参数:{}", JSON.toJSONString(parameterMap));
  34. Long start = System.currentTimeMillis();
  35. request.setAttribute(START_TIME, start);
  36. }
  37. @Around("log()")
  38. public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
  39. Object result = joinPoint.proceed();
  40. log.info("<<<<<<<返回值:{}", JSON.toJSONString(result));
  41. return result;
  42. }
  43. /**
  44. * 后置操作
  45. */
  46. @AfterReturning("log()")
  47. public void afterReturning() {
  48. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  49. HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
  50. Long start = (Long) request.getAttribute(START_TIME);
  51. Long end = System.currentTimeMillis();
  52. log.info("<<<<<<<请求耗时:{}毫秒", end - start);
  53. }
  54. }

源码阅读

添加sql执行日志

本次引入了p6spy

添加依赖

  1. <!-- Spring Aop -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-aop</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.github.gavlyukovskiy</groupId>
  8. <artifactId>p6spy-spring-boot-starter</artifactId>
  9. <version>1.6.1</version>
  10. </dependency>

修改application配置文件

url driver-class-name

  1. spring:
  2. datasource:
  3. url: jdbc:p6spy:mysql://eurekaserver01.com:2333/demo?useSSL=false&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&useSSL=false # 数据库URL
  4. username: demo # 数据库用户名
  5. password: EanD5NhsdCWLs6zs # 数据库密码
  6. driver-class-name: com.p6spy.engine.spy.P6SpyDriver

添加spy.properties文件

  1. #mybatis 3.2.1以上使用
  2. modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
  3. #mybatis 3.2.1以下使用或者不配置
  4. #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
  5. # 自定义日志打印
  6. logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
  7. #日志输出到控制台
  8. #appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
  9. # 使用日志系统记录 sql
  10. appender=com.p6spy.engine.spy.appender.Slf4JLogger
  11. # 设置 p6spy driver 代理
  12. deregisterdrivers=true
  13. # 取消JDBC URL前缀
  14. useprefix=true
  15. # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
  16. excludecategories=info,debug,result,commit,resultset
  17. # 日期格式
  18. dateformat=yyyy-MM-dd HH:mm:ss
  19. # 实际驱动可多个
  20. driverlist=com.mysql.cj.jdbc.Driver
  21. # 是否开启慢SQL记录
  22. outagedetection=true
  23. # 慢SQL记录标准 2 秒
  24. outagedetectioninterval=2

到此,p6spy会打印执行脚本。

AOP打印详细信息

  1. @Component
  2. @Slf4j
  3. @Aspect
  4. public class SqlAspectj {
  5. @Pointcut("execution(public * online.yuluo.springbootdemo.mapper.*Mapper.*(..))")
  6. public void log() {
  7. }
  8. @Around("log()")
  9. public Object sqlLogAround(ProceedingJoinPoint joinPoint) throws Throwable {
  10. String name = "-";
  11. String result = "Y";
  12. long start = System.currentTimeMillis();
  13. try {
  14. name = joinPoint.getSignature().toShortString();
  15. return joinPoint.proceed();
  16. } catch (Throwable throwable) {
  17. result = "N";
  18. throw throwable;
  19. } finally {
  20. // 执行方法名称,执行时间,执行是否成功
  21. long end = System.currentTimeMillis();
  22. log.info("name:[{}];result:[{}];time:[{}ms]", name, result, end - start);
  23. }
  24. }
  25. // 返回值
  26. @AfterReturning(pointcut = "log()", returning = "returnValue")
  27. public void afterReturning(JoinPoint joinPoint, Object returnValue) {
  28. log.info("return value is " + returnValue);
  29. }
  30. }

源码阅读