在一个项目的调试和后期维护中,日志是很重要的一部分,在 JavaWeb 中最有名的日志组件当属 log4j 了,但是在后来 Logback 貌似更厉害,SpringBoot 默认集成了 Logback,所以要在 SpringBoot 中使用 Logback 并不需要添加特别的依赖,如果有个性化需求,我们只需要修改配置文件即可。
一 打印日志
当我们启动一个 SpringBoot 项目的时候看到的那些打印其实就是 Logback 打印出来的,默认的日志级别是 info:
我们在之前的一个 HelloWorldController 中使用一下 Logger 来测试一下打印:
package com.qinshou.springbootdemo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* Description:测试 Controller
* Author: QinHao
* Date: 2019/7/25 13:45
*/
@RequestMapping(value = "/")
@RestController
public class HelloWorldController {
private final Logger mLogger = LoggerFactory.getLogger(this.getClass().getName());
@RequestMapping(value = "/", method = RequestMethod.GET)
public Map<String, String> helloWorld() {
mLogger.debug("访问了 HelloWorld 接口");
mLogger.info("访问了 HelloWorld 接口");
mLogger.warn("访问了 HelloWorld 接口");
mLogger.error("访问了 HelloWorld 接口");
Map<String, String> map = new HashMap<>();
map.put("data", "Hello World");
return map;
}
}
我们在获取 Logger 对象时是通过 LoggerFactory 工厂类获取的,需要传递一个参数,这个参数相当于 Tag,这个 Tag 最好是指定为包名,这样对于后面指定特定的日志显示级别比较方便。
访问一下 localhost:8080 可以看到添加的日志打印成功打印出来了:
二 日志配置
在配置文件中我们可以设置日志的显示级别,通过 “logging.level.Tag 名=日志级别” 来设置:
# 设置 root 日志为 INFO 级别
logging.level.root=info
# 设置指定包名日志为 debug 级别
logging.level.com.qinshou.springbootdemo=error
# 日志输出文件
logging.file=log/qinshoublog-dev.log
重新运行一下项目访问一下 localhost:8080 ,可以看到只打印了 error 级别的日志:
除了可以在配置文件中配置,我们可以写一个 logback.xml 来统一配置级别,这个文件的基本写法如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- SpringBoot 对 logback 日志的默认配置,参考 org.springframework.boot:spring-boot 依赖下 -->
<!-- 的 org/springframework/boot/logging/logback/base.xml -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE"
value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}" />
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<!-- <include resource="org/springframework/boot/logging/logback/file-appender.xml" /> -->
<!-- 重写 SpringBoot 框架的 org/springframework/boot/logging/logback/file-appender.xml 的配置 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern>
<!-- 日志保留时间 -->
<maxHistory>30</maxHistory>
<!-- 每个日志达到 10MB 时会切分日志 -->
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
这里面可以设置日志的切分大小,可以将大小先设置成了 10KB 来看看效果,多访问几次接口,就会看到日志分成了好几个文件:
三 面向切面记录日志
面向切面编程的思想我也不是很明白,但是利用 SpringBoot 的切面注解可以很方便的记录日志,这里记录一个例子:
package com.qinshou.springbootdemo.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
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 java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
/**
* Description:TODO
* Author:MrQinshou
* Date:2018/9/19 20:38
*/
@Aspect
@Component
public class LogAspect {
private final Logger mLogger = LoggerFactory.getLogger(this.getClass().getName());
@Pointcut("execution(* com.qinshou.springbootdemo.controller.*.*(..))")
public void log() {
}
/**
* author:MrQinshou
* Description:切面之前调用的方法
* date:2018/9/19 20:42
* param
* return
*/
@Before("log()")
public void before(JoinPoint joinpoint) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
String url = httpServletRequest.getRequestURL().toString();
String ip = httpServletRequest.getRemoteAddr();
String classMethod = joinpoint.getSignature().getDeclaringTypeName() + "." + joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
RequestLog requestLog = new RequestLog(url, ip, classMethod, args);
mLogger.info("Request--->{}", requestLog);
}
/**
* author:MrQinshou
* Description:切面之后调用的方法
* date:2018/9/19 20:42
* param
* return
*/
@After("log()")
public void after() {
// mLogger.info("after");
}
/**
* author:MrQinshou
* Description:捕获各个切面的返回值
* date:2018/9/19 20:44
* param
* return
*/
@AfterReturning(returning = "result", pointcut = "log()")
public void afterReturn(Object result) {
mLogger.info("afterReturn--->{}", result);
}
private class RequestLog {
private String url;
private String ip;
private String classMethod;
private Object[] args;
public RequestLog(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}
@Override
public String toString() {
return "RequestLog{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}
}
这样我们就可以在每个接口被访问时记录下日志了。