refactor(log): 新增 LogHandler 提升日志模块的复用性

This commit is contained in:
2024-12-23 20:51:42 +08:00
parent 265c669eda
commit 0d334523e9
9 changed files with 460 additions and 238 deletions

View File

@@ -19,7 +19,9 @@ package top.continew.starter.log.aspect;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
@@ -47,12 +49,47 @@ public class AccessLogAspect {
}
/**
* 切点 - 匹配所有控制器层的方法
* 切点 - 匹配所有控制器层的 GET 请求方法
*/
@Pointcut("execution(* *..controller.*.*(..)) || execution(* *..*Controller.*(..))")
@Pointcut("within(@org.springframework.web.bind.annotation.RequestMapping *)")
public void pointcut() {
}
/**
* 切点 - 匹配所有控制器层的 GET 请求方法
*/
@Pointcut("within(@org.springframework.web.bind.annotation.GetMapping *)")
public void pointcutGet() {
}
/**
* 切点 - 匹配所有控制器层的 POST 请求方法
*/
@Pointcut("within(@org.springframework.web.bind.annotation.PostMapping *)")
public void pointcutPost() {
}
/**
* 切点 - 匹配所有控制器层的 PUT 请求方法
*/
@Pointcut("within(@org.springframework.web.bind.annotation.PutMapping *)")
public void pointcutPut() {
}
/**
* 切点 - 匹配所有控制器层的 DELETE 请求方法
*/
@Pointcut("within(@org.springframework.web.bind.annotation.DeleteMapping *)")
public void pointcutDelete() {
}
/**
* 切点 - 匹配所有控制器层的 PATCH 请求方法
*/
@Pointcut("within(@org.springframework.web.bind.annotation.PatchMapping *)")
public void pointcutPatch() {
}
/**
* 打印访问日志
*
@@ -60,7 +97,7 @@ public class AccessLogAspect {
* @return 返回结果
* @throws Throwable 异常
*/
@Around("pointcut()")
@Around("pointcut() || pointcutGet() || pointcutPost() || pointcutPut() || pointcutDelete() || pointcutPatch()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Instant startTime = Instant.now();
// 非 Web 环境不记录

View File

@@ -32,15 +32,11 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import top.continew.starter.log.annotation.Log;
import top.continew.starter.log.autoconfigure.LogProperties;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.enums.Include;
import top.continew.starter.log.http.recordable.impl.RecordableServletHttpRequest;
import top.continew.starter.log.http.recordable.impl.RecordableServletHttpResponse;
import top.continew.starter.log.handler.LogHandler;
import top.continew.starter.log.model.LogRecord;
import java.lang.reflect.Method;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
/**
* 日志切面
@@ -53,12 +49,14 @@ import java.util.Set;
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
private final LogDao logDao;
private final LogProperties logProperties;
private final LogHandler logHandler;
private final LogDao logDao;
public LogAspect(LogDao logDao, LogProperties logProperties) {
this.logDao = logDao;
public LogAspect(LogProperties logProperties, LogHandler logHandler, LogDao logDao) {
this.logProperties = logProperties;
this.logHandler = logHandler;
this.logDao = logDao;
}
/**
@@ -87,7 +85,7 @@ public class LogAspect {
HttpServletResponse response = attributes.getResponse();
String errorMsg = null;
// 开始记录
LogRecord.Started startedLogRecord = LogRecord.start(startTime, new RecordableServletHttpRequest(request));
LogRecord.Started startedLogRecord = logHandler.start(startTime, request);
try {
// 执行目标方法
return joinPoint.proceed();
@@ -95,48 +93,21 @@ public class LogAspect {
errorMsg = CharSequenceUtil.sub(e.getMessage(), 0, 2000);
throw e;
} finally {
// 结束记录
this.logFinish(startedLogRecord, errorMsg, response, joinPoint);
}
}
/**
* 结束记录日志
*
* @param startedLogRecord 日志记录器
* @param errorMsg 异常信息
* @param response 响应对象
* @param joinPoint 切点
*/
private void logFinish(LogRecord.Started startedLogRecord,
String errorMsg,
HttpServletResponse response,
ProceedingJoinPoint joinPoint) {
try {
Instant endTime = Instant.now();
Method method = this.getMethod(joinPoint);
Class<?> targetClass = joinPoint.getTarget().getClass();
Log methodLog = method.getAnnotation(Log.class);
Log classLog = targetClass.getAnnotation(Log.class);
Set<Include> includeSet = this.getIncludes(methodLog, classLog);
LogRecord finishedLogRecord = startedLogRecord
.finish(endTime, new RecordableServletHttpResponse(response, response.getStatus()), includeSet);
// 记录异常信息
if (errorMsg != null) {
finishedLogRecord.setErrorMsg(errorMsg);
try {
Instant endTime = Instant.now();
Method targetMethod = this.getMethod(joinPoint);
Class<?> targetClass = joinPoint.getTarget().getClass();
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties
.getIncludes(), targetMethod, targetClass);
// 记录异常信息
if (errorMsg != null) {
logRecord.setErrorMsg(errorMsg);
}
logDao.add(logRecord);
} catch (Exception e) {
log.error("Logging http log occurred an error: {}.", e.getMessage(), e);
throw e;
}
// 记录日志描述
if (includeSet.contains(Include.DESCRIPTION)) {
this.logDescription(finishedLogRecord, methodLog);
}
// 记录所属模块
if (includeSet.contains(Include.MODULE)) {
this.logModule(finishedLogRecord, methodLog, classLog);
}
logDao.add(finishedLogRecord);
} catch (Exception e) {
log.error("Logging http log occurred an error: {}.", e.getMessage(), e);
throw e;
}
}
@@ -150,74 +121,4 @@ public class LogAspect {
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
return signature.getMethod();
}
/**
* 获取日志包含信息
*
* @param methodLog 方法级 Log 注解
* @param classLog 类级 Log 注解
* @return 日志包含信息
*/
private Set<Include> getIncludes(Log methodLog, Log classLog) {
Set<Include> includeSet = new HashSet<>(logProperties.getIncludes());
if (null != classLog) {
processInclude(includeSet, classLog);
}
if (null != methodLog) {
processInclude(includeSet, methodLog);
}
return includeSet;
}
/**
* 处理日志包含信息
*
* @param includes 日志包含信息
* @param logAnnotation Log 注解
*/
private void processInclude(Set<Include> includes, Log logAnnotation) {
Include[] includeArr = logAnnotation.includes();
if (includeArr.length > 0) {
includes.addAll(Set.of(includeArr));
}
Include[] excludeArr = logAnnotation.excludes();
if (excludeArr.length > 0) {
includes.removeAll(Set.of(excludeArr));
}
}
/**
* 记录描述
*
* @param logRecord 日志信息
* @param methodLog 方法级 Log 注解
*/
private void logDescription(LogRecord logRecord, Log methodLog) {
// 例如:@Log("新增部门") -> 新增部门
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.value())) {
logRecord.setDescription(methodLog.value());
} else {
logRecord.setDescription("请在该接口方法上指定日志描述");
}
}
/**
* 记录模块
*
* @param logRecord 日志信息
* @param methodLog 方法级 Log 注解
* @param classLog 类级 Log 注解
*/
private void logModule(LogRecord logRecord, Log methodLog, Log classLog) {
// 例如:@Log(module = "部门管理") -> 部门管理
// 优先使用方法注解的模块
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.module())) {
logRecord.setModule(methodLog.module());
return;
}
// 其次使用类注解的模块
if (null != classLog) {
logRecord.setModule(CharSequenceUtil.blankToDefault(classLog.module(), "请在该接口类上指定所属模块"));
}
}
}

View File

@@ -29,6 +29,8 @@ import top.continew.starter.log.aspect.AccessLogAspect;
import top.continew.starter.log.aspect.LogAspect;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.dao.impl.DefaultLogDaoImpl;
import top.continew.starter.log.handler.AopLogHandler;
import top.continew.starter.log.handler.LogHandler;
/**
* 日志自动配置
@@ -53,12 +55,14 @@ public class LogAutoConfiguration {
/**
* 日志切面
*
* @param logHandler 日志处理器
* @param logDao 日志持久层接口
* @return {@link LogAspect }
*/
@Bean
@ConditionalOnMissingBean
public LogAspect logAspect(LogDao logDao) {
return new LogAspect(logDao, logProperties);
public LogAspect logAspect(LogHandler logHandler, LogDao logDao) {
return new LogAspect(logProperties, logHandler, logDao);
}
/**
@@ -72,6 +76,15 @@ public class LogAutoConfiguration {
return new AccessLogAspect(logProperties);
}
/**
* 日志处理器
*/
@Bean
@ConditionalOnMissingBean
public LogHandler logHandler() {
return new AopLogHandler();
}
/**
* 日志持久层接口
*/

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.log.handler;
import cn.hutool.core.text.CharSequenceUtil;
import top.continew.starter.log.model.LogRecord;
import java.lang.reflect.Method;
/**
* 日志处理器-AOP 版实现
*
* @author Charles7c
* @since 2.8.0
*/
public class AopLogHandler extends AbstractLogHandler {
@Override
public void logDescription(LogRecord logRecord, Method targetMethod) {
super.logDescription(logRecord, targetMethod);
if (CharSequenceUtil.isBlank(logRecord.getDescription())) {
logRecord.setDescription("请在该接口方法上指定日志描述");
}
}
@Override
public void logModule(LogRecord logRecord, Method targetMethod, Class<?> targetClass) {
super.logModule(logRecord, targetMethod, targetClass);
if (CharSequenceUtil.isBlank(logRecord.getModule())) {
logRecord.setModule("请在该接口类上指定所属模块");
}
}
}