SpringBoot/Cloud AOP 统一日志输出_sed


SpringBoot/Cloud AOP 统一日志输出_spring_02

文章目录

1. 导入依赖
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 数据序列化-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
2. aop拦截器
package com.course.server.config;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.course.server.util.UuidUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
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.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;

@Aspect
@Component
public class LogAspect {

private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);

/** 定义一个切点 */
@Pointcut("execution(public * com.course.*.controller..*Controller.*(..))")
public void controllerPointcut() {}

@Before("controllerPointcut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 日志编号
MDC.put("UUID", UuidUtil.getShortUuid());

// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Signature signature = joinPoint.getSignature();
String name = signature.getName();

// 打印业务操作
String nameCn = "";
if (name.contains("list") || name.contains("query")) {
nameCn = "查询";
} else if (name.contains("save")) {
nameCn = "保存";
} else if (name.contains("delete")) {
nameCn = "删除";
} else {
nameCn = "操作";
}

// 使用反射,获取业务名称
Class clazz = signature.getDeclaringType();
Field field;
String businessName = "";
try {
field = clazz.getField("BUSINESS_NAME");
if (!StringUtils.isEmpty(field)) {
businessName = (String) field.get(clazz);
}
} catch (NoSuchFieldException e) {
LOG.error("未获取到业务名称");
} catch (SecurityException e) {
LOG.error("获取业务名称失败", e);
}

// 打印请求信息
LOG.info("------------- 【{}】{}开始 -------------", businessName, nameCn);
LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
LOG.info("远程地址: {}", request.getRemoteAddr());

// 打印请求参数
Object[] args = joinPoint.getArgs();
Object[] arguments = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ServletRequest
|| args[i] instanceof ServletResponse
|| args[i] instanceof MultipartFile) {
continue;
}
arguments[i] = args[i];
}
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"shard"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter)); // 为空的会不打印,但是像图片等长字段也会打印
}

@Around("controllerPointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"password", "shard"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter));
LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
return result;
}

}
3. logback配置

SpringBoot/Cloud AOP 统一日志输出_sed_03


logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 修改一下路径-->
<property name="PATH" value="/log/imooc/course/business"></property>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %green(%-8X{UUID}) %msg%n</Pattern>-->
<Pattern>%d{ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %green(%-8X{UUID}) %msg%n</Pattern>
</encoder>
</appender>

<appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/trace.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-8X{UUID}) %msg%n</pattern>
</layout>
</appender>

<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-8X{UUID}) %msg%n</pattern>
</layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<root level="ERROR">
<appender-ref ref="ERROR_FILE" />
</root>

<root level="TRACE">
<appender-ref ref="TRACE_FILE" />
</root>

<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
4. 测试类
package com.course.business.controller.admin;

import com.course.server.dto.ChapterDto;
import com.course.server.dto.PageDto;
import com.course.server.dto.ResponseDto;
import com.course.server.service.ChapterService;
import com.course.server.util.ValidatorUtil;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@RequestMapping("/admin/chapter")
public class ChapterController {
// 业务标识
public static final String BUSINESS_NAME = "大章";

@Resource
private ChapterService chapterService;

//http://127.0.0.1:9002/business/admin/chapter/list
@PostMapping("/list")
public ResponseDto list(@RequestBody PageDto pageDto) {
ResponseDto responseDto = new ResponseDto();
chapterService.list(pageDto);
responseDto.setContent(pageDto);
return responseDto;
}

@PostMapping("/save")
public ResponseDto save(@RequestBody ChapterDto chapterDto) {
// 保存校验
ValidatorUtil.require(chapterDto.getName(), "名称");
ValidatorUtil.require(chapterDto.getCourseId(), "课程ID");
ValidatorUtil.length(chapterDto.getCourseId(), "课程ID", 1, 8);

ResponseDto responseDto = new ResponseDto();
chapterService.save(chapterDto);
responseDto.setContent(chapterDto);
return responseDto;
}

@DeleteMapping("/delete/{id}")
public ResponseDto delete(@PathVariable String id) {
ResponseDto responseDto = new ResponseDto();
chapterService.delete(id);
return responseDto;
}
}
5. 关键点

LogAspect中日志key “UUID”要和logback.xml中的获取的key UUID要一致,UUID生成规则自定义

SpringBoot/Cloud AOP 统一日志输出_aop_04


SpringBoot/Cloud AOP 统一日志输出_aop_05

6. 效果图

SpringBoot/Cloud AOP 统一日志输出_sed


SpringBoot/Cloud AOP 统一日志输出_spring_02

SpringBoot/Cloud AOP 统一日志输出_json_08


SpringBoot/Cloud AOP 统一日志输出_aop_09