日志对于应用程序的重要性不言而喻,不管是记录运行情况还是追踪线上问题,都离不开对日志的分析,在 Java 领域里存在着多种日志框架,如 JUL, Log4j, Log4j2, Commons Loggin, Slf4j, Logback 等。 Spring Boot 默认已经使用了 SLF4J + LogBack 。在Spring框架中,使用AOP配合自定义注解可以方便的实现用户操作的监控。
Lombok是一个通过注解以达到减少代码的Java库,如通过注解的方式减少get,set方法,构造方法等。lombok的@Slf4j
注解用来记录日志比较方便。用到lombok后,只需要在类上加个注解即可。
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
@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;
}
定义一个LogAspect类,使用@Aspect
标注让其成为一个切面,切点为使用@Log
注解标注的方法,使用@Around
环绕通知
@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());
}
}
SpringBoot底层默认使用slf4j+logback的方式进行日志记录 ,这里简单介绍一下logback的使用。logbac日志级别从低到高分别为TRACE, DEBUG, INFO, WARN, ERROR
我们一般是在类路径下建立一个logback.xml,也可命名为(logback-spring.xml , logback-spring.groovy , logback.xml ,logback.groovy)
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>spring-boot-logging</contextName>
<property name="log.path" value="log" />
<property name="log.maxHistory" value="15" />
<property name="log.colorPattern" value="%magenta(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %yellow(%thread) %green(%logger) %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level %thread %logger %msg%n"/>
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.colorPattern}</pattern>
</encoder>
</appender>
<!--输出到文件-->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<MaxHistory>${log.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/error.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="debug">
<appender-ref ref="console" />
</root>
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>
<configuration></configuration>
<contextName>spring-boot-logging</contextName>
每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称
用来定义变量值的标签, 有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量
<property name="log.path" value="log" />
appender用来格式化日志输出节点,有俩个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.colorPattern}</pattern>
</encoder>
</appender>
<!--输出到文件-->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<MaxHistory>${log.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
filter
是一个过滤器,表示对输出到控制台的日记进行过滤。有两种过滤器,分别为LevelFilter
和ThresholdFilter
。执行一个过滤器会有返回个枚举值,即DENY,NEUTRAL,ACCEPT其中之一。返回DENY,日志将立即被抛弃不再经过其他过滤器;返回NEUTRAL,有序列表里的下个过滤器接着处理日志;返回ACCEPT,日志会被立即处理,不再经过剩余过滤器。
其中LevelFilter
为级别过滤器,根据日志级别进行过滤。其下有三个子节点,level表示过滤的级别,用于配置符合过滤条件的操作,ACCEPT符合级别的输出到控制台,用于配置不符合过滤条件的操作,DENY不符合的拒绝输出到控制台。
ThresholdFilter
为临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。
常见的日志输出到文件,随着应用的运行时间越来越长,日志也会增长的越来越多,将他们输出到同一个文件并非一个好办法。RollingFileAppender用于切分文件日志。
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"
是最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责触发滚动
<fileNamePattern>${log.path}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
定义了日志的切分方式——把每一天的日志归档到一个文件中,同理,可以使用%d{yyyy-MM-dd}
来定义精确到分的日志切分方式。
<MaxHistory>${log.maxHistory}</MaxHistory>
表示只保留最近30天的日志
<root level="debug">
<appender-ref ref="console" />
</root>
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性,默认是DEBUG。其中可以包含零个或多个元素,表示我们定义的appender将会添加到我们定义的loger子节点中
为了验证这种方式是否真的能拿到注解中携带的日志信息,这里创建一个Controller,代码如下
@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) { }
}
访问接口后我们可以在控制台看到打印出来的日志
同时,在log文件夹下生成了日志文件
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。