# SpringBoot记录日志 **Repository Path**: jianml/log ## Basic Information - **Project Name**: SpringBoot记录日志 - **Description**: SpringBoot记录日志 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 1 - **Created**: 2020-01-08 - **Last Updated**: 2022-12-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringBoot记录日志 #### 介绍 日志对于应用程序的重要性不言而喻,不管是记录运行情况还是追踪线上问题,都离不开对日志的分析,在 Java 领域里存在着多种日志框架,如 JUL, Log4j, Log4j2, Commons Loggin, Slf4j, Logback 等。 Spring Boot 默认已经使用了 **SLF4J + LogBack** 。在Spring框架中,使用AOP配合自定义注解可以方便的实现用户操作的监控。 ## 依赖引入 Lombok是一个通过注解以达到减少代码的Java库,如通过注解的方式减少get,set方法,构造方法等。lombok的`@Slf4j`注解用来记录日志比较方便。用到lombok后,只需要在类上加个注解即可。 ```xml org.springframework.boot spring-boot-starter-aop org.projectlombok lombok true ``` ## 自定义注解 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String value() default ""; } ``` ## 创建日志实体 ```java @Data public class SysLog { /** * 日志ID */ private Integer id; /** * 操作用户 */ private String username; /** * 操作内容 */ private String operation; /** * 耗时 */ private Integer time; /** * 操作方法 */ private String method; /** * 方法参数 */ private String params; /** * 操作者IP */ private String ip; /** * 创建时间 */ private Date createTime; } ``` ## 使用AOP统一处理日志 定义一个LogAspect类,使用`@Aspect`标注让其成为一个切面,切点为使用`@Log`注解标注的方法,使用`@Around`环绕通知 ```java @Slf4j @Aspect @Component public class LogAspect { @Pointcut("@annotation(cn.jianml.log.annotation.Log)") public void pointcut() { // do nothing } @Around("pointcut()") public Object around(ProceedingJoinPoint point) { Object result = null; long beginTime = System.currentTimeMillis(); try{ // 执行方法 result = point.proceed(); }catch (Throwable throwable) { throwable.printStackTrace(); } long time = System.currentTimeMillis() - beginTime; // 打印日志 printLog(point, time); return result; } private void printLog(ProceedingJoinPoint joinPoint, long time){ MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLog sysLog = new SysLog(); Log logAnnotation = method.getAnnotation(Log.class); if(logAnnotation != null) { // 注解上的描述 sysLog.setOperation(logAnnotation.value()); } // 请求方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); sysLog.setMethod(className + "." + methodName + "()"); // 请求的方法参数值 Object[] args = joinPoint.getArgs(); // 请求的方法参数名称 LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); String[] paramNames = u.getParameterNames(method); if (args != null && paramNames != null) { String params = ""; for (int i = 0; i < args.length; i++) { params += " " + paramNames[i] + ": " + args[i]; } sysLog.setParams(params); } // 获取request HttpServletRequest request = HttpContextUtil.getHttpServletRequest(); // 设置IP地址 sysLog.setIp(IPUtil.getIpAddr(request)); // 模拟一个用户名 sysLog.setUsername("admin"); sysLog.setTime((int) time); sysLog.setCreateTime(new Date()); // 打印系统日志(我们也可以在这里把系统日志存入数据库) log.info(sysLog.toString()); } } ``` ## Logback的使用 SpringBoot底层默认使用slf4j+logback的方式进行日志记录 ,这里简单介绍一下logback的使用。logbac日志级别从低到高分别为TRACE, DEBUG, INFO, WARN, ERROR 我们一般是在类路径下建立一个logback.xml,也可命名为(logback-spring.xml , logback-spring.groovy , logback.xml ,logback.groovy) ```xml spring-boot-logging ${log.colorPattern} ${log.path}/info/info.%d{yyyy-MM-dd}.log ${log.maxHistory} ${log.pattern} INFO ACCEPT DENY ${log.path}/error/error.%d{yyyy-MM-dd}.log ${log.pattern} ERROR ACCEPT DENY ``` ### 根节点包含的属性 ```xml ``` - scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true - scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟 - debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false ### 设置上下文名称 ```xml spring-boot-logging ``` 每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称 #### 设置变量 用来定义变量值的标签, 有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量 ```xml ``` ### 子节点appender appender用来格式化日志输出节点,有俩个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。 #### 控制台输出ConsoleAppender ```xml ${log.colorPattern} ``` #### 输出到文件RollingFileAppender ```xml ${log.path}/info/info.%d{yyyy-MM-dd}.log ${log.maxHistory} ${log.pattern} INFO ACCEPT DENY ``` `filter`是一个过滤器,表示对输出到控制台的日记进行过滤。有两种过滤器,分别为`LevelFilter `和`ThresholdFilter`。执行一个过滤器会有返回个枚举值,即DENY,NEUTRAL,ACCEPT其中之一。返回DENY,日志将立即被抛弃不再经过其他过滤器;返回NEUTRAL,有序列表里的下个过滤器接着处理日志;返回ACCEPT,日志会被立即处理,不再经过剩余过滤器。 其中`LevelFilter`为级别过滤器,根据日志级别进行过滤。其下有三个子节点,level表示过滤的级别,用于配置符合过滤条件的操作,ACCEPT符合级别的输出到控制台,用于配置不符合过滤条件的操作,DENY不符合的拒绝输出到控制台。 `ThresholdFilter`为临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。 常见的日志输出到文件,随着应用的运行时间越来越长,日志也会增长的越来越多,将他们输出到同一个文件并非一个好办法。RollingFileAppender用于切分文件日志。 `class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"`是最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责触发滚动 `${log.path}/info/info.%d{yyyy-MM-dd}.log`定义了日志的切分方式——把每一天的日志归档到一个文件中,同理,可以使用`%d{yyyy-MM-dd}`来定义精确到分的日志切分方式。 `${log.maxHistory}`表示只保留最近30天的日志 #### 子节点root ```xml ``` root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性,默认是DEBUG。其中可以包含零个或多个元素,表示我们定义的appender将会添加到我们定义的loger子节点中 ## 测试 为了验证这种方式是否真的能拿到注解中携带的日志信息,这里创建一个Controller,代码如下 ```java @RestController public class TestController { @Log("执行方法一") @GetMapping("one") public void methodOne(String name){} @Log("执行方法二") @GetMapping("/two") public void methodTwo() throws InterruptedException { Thread.sleep(2000); } @Log("执行方法三") @GetMapping("/three") public void methodThree(String name, String age) { } } ``` 访问接口后我们可以在控制台看到打印出来的日志 ![mark](http://image.jianml.cn/image/20200108/op66aWitAGv9.png) 同时,在log文件夹下生成了日志文件 ![mark](http://image.jianml.cn/image/20200108/pxFf7AMVxvTt.png) > 源码地址:https://gitee.com/jianml/log