在日常开发过程中,我们会打印各种各样的日志。那么如何记录一次请求的RT、方法的入参、结果。这边就想着怎么可以一劳永逸,不用在每个方法上打上日志的注解?同时,日志的格式还是json的形式存在。那就再方便不过了。
具体的流程如下:
最后决定使用方法拦截器实现。具体参考如下:
1、引入Maven坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
2、自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceGuard {
String module() default "";
}
3、日志接口
日志注解增强器
public class ServiceLoggerAnnotationHelper {
// 日志模块名称
private static final ConcurrentHashMap<Method, String> LOGGER_MODULE_NAMES = new ConcurrentHashMap<>();
// 记录器没有注释
private static final Set<Method> LOGGER_WITHOUT_ANNOTATIONS = new HashSet<>();
public static Optional<String> getLoggerModuleName(Method method) {
// 校验是否已经存在,存在直接返回
String name = LOGGER_MODULE_NAMES.get(method);
if (StringUtils.hasLength(name)) {
return Optional.of(name);
}
if (LOGGER_WITHOUT_ANNOTATIONS.contains(method)) {
return Optional.empty();
}
ServiceGuard annotation = method.getDeclaringClass().getAnnotation(ServiceGuard.class);
if (null == annotation) {
LOGGER_WITHOUT_ANNOTATIONS.add(method);
return Optional.empty();
}
name = annotation.module();
LOGGER_MODULE_NAMES.putIfAbsent(method, name);
return Optional.of(name);
}
}
日志接口
public interface IServiceLogger<T> {
/**
* 异常日志处理
*
* @param e 异常
* @return 结果
*/
T failedException(Throwable e) throws Throwable;
Class<T> getResultClass();
String getResultCode(Object result);
String getResultMessage(Object result);
String getLoggerTitle(Method method);
IServiceLogger DEFAULT_LOGGER_HELPER = new IServiceLogger() {
@Override
public Object failedException(Throwable e) throws Throwable {
throw e;
}
@Override
public Class getResultClass() {
return Object.class;
}
@Override
public String getResultCode(Object result) {
return "";
}
@Override
public String getResultMessage(Object result) {
return "";
}
@Override
public String getLoggerTitle(Method method) {
Optional<String> name = ServiceLoggerAnnotationHelper.getLoggerModuleName(method);
return name.orElse("INNER_SERVICE");
}
};
}
服务Response
@Data
public class ServiceResult<T> implements Serializable {
protected boolean isSuccess;
protected String code;
protected String message;
protected T result;
public static <T> ServiceResult<T> failed(String errorCode, String errorMsg) {
ServiceResult<T> result = new ServiceResult<>();
result.isSuccess = false;
result.code = errorCode;
result.message = errorMsg;
return result;
}
}
实现类
public class ServiceResultLogger implements IServiceLogger<ServiceResult> {
@Override
public ServiceResult failedException(Throwable e) throws Throwable {
return ServiceResult.failed(e.getClass().getCanonicalName(), e.getLocalizedMessage());
}
@Override
public Class getResultClass() {
return ServiceResult.class;
}
@Override
public String getResultCode(Object result) {
return ((ServiceResult)result).getCode();
}
@Override
public String getResultMessage(Object result) {
return ((ServiceResult)result).getMessage();
}
@Override
public String getLoggerTitle(Method method) {
Optional<String> name = ServiceLoggerAnnotationHelper.getLoggerModuleName(method);
return name.orElse("SERVICE");
}
}
4、方法拦截器
public class ServiceInterceptor implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger("RUNTIME_LOG");
private final Map<Class<?>, IServiceLogger> loggerHelpers = new HashMap<>();
private List<IServiceLogger> serviceLoggers;
@PostConstruct
public void init() {
serviceLoggers.forEach(o -> {
loggerHelpers.put(o.getResultClass(), o);
});
}
public void setServiceLoggers(List<IServiceLogger> serviceLoggers) {
this.serviceLoggers = serviceLoggers;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取对应的方法
Method method = invocation.getMethod();
IServiceLogger<?> loggerHelper = loggerHelpers.get(method.getReturnType());
if (null == loggerHelper) {
loggerHelper = IServiceLogger.DEFAULT_LOGGER_HELPER;
}
// 服务名
String serviceName = method.getDeclaringClass().getCanonicalName();
// 方法名
String methodName = method.getName();
// 开始时间
long startTime = System.currentTimeMillis();
// 调用结果
Object result = null;
// 异常信息
Throwable exception = null;
try {
result = invocation.proceed();
return result;
} catch (Throwable e) {
exception = e;
return result;
} finally {
// 打印日志
String errorCode;
String errorMsg;
if (null == result || IServiceLogger.DEFAULT_LOGGER_HELPER.equals(loggerHelper)
|| !loggerHelper.getResultClass().isAssignableFrom(result.getClass())) {
errorCode = null == exception ? "SUCCESS" : exception.getClass().getCanonicalName();
errorMsg = null == exception ? "SUCCESS" : exception.getMessage();
} else {
errorCode = loggerHelper.getResultCode(result);
errorMsg = loggerHelper.getResultMessage(result);
}
if (null == exception) {
logger.info("|" + loggerHelper.getLoggerTitle(method) + "|{}|{}|{}|{}|{}|args:{}|result:{}|", serviceName,
methodName, System.currentTimeMillis() - startTime, errorCode, errorMsg,
JSON.toJSONString(invocation.getArguments()), JSON.toJSONString(result));
} else {
logger.error("|" + loggerHelper.getLoggerTitle(method) + "|" + serviceName + "|" + methodName + "|" + (
System.currentTimeMillis() - startTime) + "|" + errorCode + "|" + errorMsg + "|args:"
+ JSON.toJSONString(invocation.getArguments()) + "|result:" + JSON.toJSONString(result), exception);
}
}
}
}
5、Spring统一管理(CommonAutoConfiguration)
@Configuration
public class CommonAutoConfiguration {
@Bean
public ServiceInterceptor serviceInterceptor(@Autowired List<IServiceLogger> serviceLoggers) {
ServiceInterceptor interceptor = new ServiceInterceptor();
interceptor.setServiceLoggers(serviceLoggers);
return interceptor;
}
@Bean
public ServiceResultLogger serviceResultLogger() {
return new ServiceResultLogger();
}
@Bean
public Advisor walletLoggerInterceptor(@Autowired ServiceInterceptor serviceInterceptor) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
StringJoiner sj = new StringJoiner(" or ");
sj.add("@within(com.example.demo.ServiceGuard)");
pointcut.setExpression(sj.toString());
return new DefaultPointcutAdvisor(pointcut, serviceInterceptor);
}
}
6、测试结果
@RestController
@RequestMapping("/test")
// 打上我们自己的注解即可
@ServiceGuard(module = "aaa")
public class TestCtrl {
@RequestMapping("/a")
public ServiceResult<AModel> a(@RequestBody AModel model){
return ServiceResult.failed("a","错误");
}
}
结果截图:
7、结合SLS日志
附上源码地址:gitee.com/lby6220/log…