前言
大型完善項目中肯定是需要一個全局日志攔截,記錄每次接口通路相關資訊,包括:
通路ip,通路裝置,請求參數,響應結果,響應時間,開始請求時間,通路接口描述,通路的使用者,接口位址,請求類型,便于項目的調試追蹤
整合日志
SpringBoot已經幫我們做了日志整合,在它的父pom項中
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
我們點進去看到還有父pom項
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.7</version>
</parent>
spring-boot-dependencies
幫我自動整合所有jar包版本,和内置很多啟動項start,這是SpringBoot使用起來不需要配置那麼多jar包依賴關系的本質,點進去我們看到了,所有spring,springmvc所需要依賴,和jar版本
最終我們找到了日志依賴所需jar包,是以我們直接使用就行,不需要額外引入
配置日志
######日志配置######
# 日志檔案
logging.config=classpath:logback-spring.xml
#日志包級别
#logging.level.root=info
logging.level.cn.soboys.blogapi=info
#日志存儲路徑
logging.file.path=/Users/xiangyong/selfProject/project/blog-api/log
日志檔案配置
這裡日志名字
logback-spring.xml
是SpringBoot獨有,這麼寫spring boot可以為它添加一些spring boot特有的配置項
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>shop-api</contextName>
<!--定義日志檔案的存儲位址 從springboot配置檔案中擷取路徑-->
<springProperty scope="context" name="LOG_PATH" source="logging.file.path"/>
<!--springboot配置檔案中擷取日志級别-->
<springProperty scope="context" name="LOG_LEVEL" source="logging.level.root"/>
<!-- <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>
這裡我把日志分成兩個日志檔案,一個錯誤日志,一個資訊日志,按照明天一個日志檔案方式
全局日志記錄
這裡我們基于aop切面進行請求攔截,記錄所有請求相關資訊
package cn.soboys.core;
import lombok.Data;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 18:48
* 日志資訊
*/
@Data
public class LogSubject {
/**
* 操作描述
*/
private String description;
/**
* 操作使用者
*/
private String username;
/**
* 操作時間
*/
private String startTime;
/**
* 消耗時間
*/
private String spendTime;
/**
* URL
*/
private String url;
/**
* 請求類型
*/
private String method;
/**
* IP位址
*/
private String ip;
/**
* 請求參數
*/
private Object parameter;
/**
* 請求傳回的結果
*/
private Object result;
/**
* 城市
*/
private String city;
/**
* 請求裝置資訊
*/
private String device;
}
這裡用到了aop 是以要導入aop相關包
<!--aop 切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
編寫切面類攔截對應的
controller
請求
package cn.soboys.core;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 14:52
* 切面
*/
public class BaseAspectSupport {
public Method resolveMethod(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature)point.getSignature();
Class<?> targetClass = point.getTarget().getClass();
Method method = getDeclaredMethod(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("無法解析目标方法: " + signature.getMethod().getName());
}
return method;
}
private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
try {
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
return getDeclaredMethod(superClass, name, parameterTypes);
}
}
return null;
}
}
日志記錄切面
GlobalLogAspect
package cn.soboys.core;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 15:22
* 全局日志記錄器
*/
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
/**
* 定義切面Pointcut
*/
@Pointcut("execution(public * cn.soboys.blogapi.controller.*.*(..))")
public void log() {
}
/**
* 環繞通知
*
* @param joinPoint
* @return
*/
@Around("log()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
LogSubject logSubject = new LogSubject();
//記錄時間定時器
TimeInterval timer = DateUtil.timer(true);
//執行結果
Object result = joinPoint.proceed();
logSubject.setResult(result);
//執行消耗時間
String endTime = timer.intervalPretty();
logSubject.setSpendTime(endTime);
//執行參數
Method method = resolveMethod(joinPoint);
logSubject.setParameter(getParameter(method, joinPoint.getArgs()));
HttpServletRequest request = HttpContextUtil.getRequest();
// 接口請求時間
logSubject.setStartTime(DateUtil.now());
//請求連結
logSubject.setUrl(request.getRequestURL().toString());
//請求方法GET,POST等
logSubject.setMethod(request.getMethod());
//請求裝置資訊
logSubject.setDevice(HttpContextUtil.getDevice());
//請求位址
logSubject.setIp(HttpContextUtil.getIpAddr());
//接口描述
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
logSubject.setDescription(apiOperation.value());
}
String a = JSONUtil.toJsonPrettyStr(logSubject);
log.info(a);
return result;
}
/**
* 根據方法和傳入的參數擷取請求參數
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < parameters.length; i++) {
//将RequestBody注解修飾的參數作為請求參數
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
//将RequestParam注解修飾的參數作為請求參數
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
String key = parameters[i].getName();
if (requestBody != null) {
argList.add(args[i]);
} else if (requestParam != null) {
map.put(key, args[i]);
} else {
map.put(key, args[i]);
}
}
if (map.size() > 0) {
argList.add(map);
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}
package cn.soboys.core;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author kenx
* @version 1.0
* @date 2021/6/18 15:22
* 全局日志記錄器
*/
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
/**
* 定義切面Pointcut
*/
@Pointcut("execution(public * cn.soboys.blogapi.controller.*.*(..))")
public void log() {
}
/**
* 環繞通知
*
* @param joinPoint
* @return
*/
@Around("log()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
LogSubject logSubject = new LogSubject();
//記錄時間定時器
TimeInterval timer = DateUtil.timer(true);
//執行結果
Object result = joinPoint.proceed();
logSubject.setResult(result);
//執行消耗時間
String endTime = timer.intervalPretty();
logSubject.setSpendTime(endTime);
//執行參數
Method method = resolveMethod(joinPoint);
logSubject.setParameter(getParameter(method, joinPoint.getArgs()));
HttpServletRequest request = HttpContextUtil.getRequest();
// 接口請求時間
logSubject.setStartTime(DateUtil.now());
//請求連結
logSubject.setUrl(request.getRequestURL().toString());
//請求方法GET,POST等
logSubject.setMethod(request.getMethod());
//請求裝置資訊
logSubject.setDevice(HttpContextUtil.getDevice());
//請求位址
logSubject.setIp(HttpContextUtil.getIpAddr());
//接口描述
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
logSubject.setDescription(apiOperation.value());
}
String a = JSONUtil.toJsonPrettyStr(logSubject);
log.info(a);
return result;
}
/**
* 根據方法和傳入的參數擷取請求參數
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < parameters.length; i++) {
//将RequestBody注解修飾的參數作為請求參數
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
//将RequestParam注解修飾的參數作為請求參數
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
String key = parameters[i].getName();
if (requestBody != null) {
argList.add(args[i]);
} else if (requestParam != null) {
map.put(key, args[i]);
} else {
map.put(key, args[i]);
}
}
if (map.size() > 0) {
argList.add(map);
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}
這樣我們在每次請求接口的時候都會記錄請求的資訊:
詳細日志學習SpringBoot日志使用請參考我這兩篇文章
SpringBoot日志詳細使用和配置
JAVA 中日志的記錄于使用
關注公衆号猿小叔擷取更多幹貨分享