SpringBoot项目中基于AOP特性打印接口日志
新建SpringBoot项目
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LogApplication {
public static void main(String[] args) {
SpringApplication.run(LogApplication.class, args);
}
}
引入web、aop和log4j2依赖包
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-aop')
compile('org.springframework.boot:spring-boot-starter-log4j2')
}
新增log4j2配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- t01Synchronizedon't forget to set system property
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
to make all loggers asynchronous. -->
<!-- status="OFF",可以去掉,它的含义为是否记录log4j2本身的event信息,默认是OFF -->
<configuration status="OFF">
<!-- 定义下面的引用名 -->
<Properties>
<!-- <property name="basePath">系统日志位置</property> -->
<property name="basePath">/web/demo/log</property>
<!-- 暂时业务都输出到同一个日志文件 -->
<property name="server">${basePath}/web-log</property>
</Properties>
<!--先定义所有的appender -->
<appenders>
<!--输出控制台的配置 -->
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<!--输出日志的格式 -->
<PatternLayout
pattern="%d{HH:mm:ss,SSS} [%t01Synchronized] %X{X-B3-TraceId} %X{X-B3-SpanId} %X{X-B3-ParentSpanId} %-5level %logger{36} %L %M - %msg%n"/>
</Console>
<!--打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
<!-- 按月生成归档日志,可以使用 filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}aspect.log" -->
<RollingRandomAccessFile name="RollingRandomAccessFile"
filename="${server}.log" filePattern="${server}-%d{yyyy-MM-dd}.log"
append="true">
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout>
<Pattern>[%d{yyyy-MM-dd HH:mm:ss,SSS}] [%t01Synchronized] [%X{X-B3-TraceId}] [%X{X-B3-SpanId}] [%X{X-B3-ParentSpanId}] [%-5level] [%class{36}] [%L] [%M] [%msg%xEx]%n</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="100 MB"/>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<DefaultRolloverStrategy max="2"/>
</RollingRandomAccessFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
<loggers>
<root level="info">
<appender-ref ref="RollingRandomAccessFile"/>
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
自定义切入点
import java.lang.annotation.*;
/**
* @Author: vhtk
* @Description:
* @Date: 2020/6/22
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WriteLog {
}
自定义切面
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import java.lang.reflect.Method;
/**
* @Author: vhtk
* @Description:
* @Date: 2020/6/22
*/
@Component
@Aspect
@Profile("default")
public class WriteLogHandler {
@Pointcut(value = "@annotation(com.example.log.aspect.WriteLog) || execution(* com.example.log..*Controller.*(..))")
public void pointCut() {
}
/**
* 日志
*/
private static Logger logger = LoggerFactory.getLogger(WriteLogHandler.class);
/**
* 字符数量
*/
private static final int LOG_SIZE = 2048;
/**
* 切入点声明
*/
private static String LINE_SPLIT = "\n|\t\t\t";
/**
* 方法切入点(输出方法执行概要日志)
*
* @param joinPoint 连接点
* @return 原方法返回值
* @throws Throwable 异常
*/
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
printStart(sb);
sb.append(LINE_SPLIT);
sb.append("执行方法:");
buildMethodInfo(joinPoint, sb);
sb.append(LINE_SPLIT);
try {
Object retVal = joinPoint.proceed();
String json = JSONObject.toJSONString(retVal);
if (!StringUtils.isEmpty(json) && json.length() >= LOG_SIZE) {
json = json.substring(0, LOG_SIZE);
}
sb.append("返回值:").append(json);
return retVal;
} catch (Throwable ex) {
sb.append("发生异常:").append(ex.getMessage());
throw ex;
} finally {
//调用
sb.append(LINE_SPLIT);
sb.append("耗时(ms):");
//计算方法耗时
sb.append(System.currentTimeMillis() - startTime);
printEnd(sb);
logger.info(sb.toString());
}
}
private void buildMethodInfo(JoinPoint joinPoint, StringBuilder stringBuilder) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//方法
stringBuilder.append(method.toGenericString());
stringBuilder.append(LINE_SPLIT);
stringBuilder.append("参数依次为:");
for (int i = 0; i < joinPoint.getArgs().length; i++) {
stringBuilder.append(LINE_SPLIT);
stringBuilder.append("\t\t");
stringBuilder.append(i);
stringBuilder.append(":");
Object argument = joinPoint.getArgs()[i];
if (argument instanceof ServletRequest) {
stringBuilder.append(argument);
} else {
stringBuilder.append(JSONObject.toJSON(argument));
}
}
}
private void printStart(StringBuilder stringBuilder) {
stringBuilder.append("\n+-----------------");
}
private void printEnd(StringBuilder stringBuilder) {
stringBuilder.append("\n\\__________________");
}
}
全局异常处理器
package com.example.log.aspect;
import com.example.log.common.BusinessException;
import com.example.log.common.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* @Author: vhtk
* @Description:
* @Date: 2020/6/22
*/
@ControllerAdvice
public class ControllerExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
@ExceptionHandler(Throwable.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handleException(Throwable e, HttpServletRequest req) {
logger.error("Catch business exception: {}", e.getMessage(), e);
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException bind = (MethodArgumentNotValidException) e;
List<ObjectError> allErrors = bind.getBindingResult().getAllErrors();
StringBuffer message = new StringBuffer();
allErrors.forEach(error -> message.append(error.getDefaultMessage()).append(";"));
return Result.fail("请求参数错误" + req.getRequestURL().toString(), message.toString());
} else if (e instanceof BusinessException) {
BusinessException businessException = (BusinessException) e;
return Result.info(businessException.getCode(), "请求发生异常" + req.getRequestURL().toString(), businessException.getMessage());
} else {
return Result.fail("请求发生异常" + req.getRequestURL().toString(), e.getMessage());
}
}
}
测试代码
import com.example.log.aspect.WriteLog;
import com.example.log.dto.TestDto;
import com.example.log.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @Author: vhtk
* @Description:
* @Date: 2020/6/22
*/
@RestController
public class TestController {
@Autowired
private TestService testService;
@GetMapping("/get")
public Integer testGet(@RequestParam Integer id){
testService.testGet(id);
return id;
}
@PostMapping("/post")
@WriteLog
public void testPost(@RequestBody TestDto testDto){
testService.testPost(testDto);
}
@PutMapping("/put")
public void testPut(@RequestBody TestDto testDto){
testService.testPut(testDto);
}
@DeleteMapping("/delete")
public void testDelete(@RequestParam Integer id){
testService.testDelete(id);
}
}
运行效果
接口正常返回
接口异常