業(yè)務(wù)背景
我們希望可以在使用日志攔截器時,定義屬于自己的攔截器方法。
實現(xiàn)的方式有很多種,我們分別來看一下。
拓展閱讀
JAVA 注解結(jié)合 spring aop 實現(xiàn)自動輸出日志[1]
java 注解結(jié)合 spring aop 實現(xiàn)日志traceId唯一標識[2]
java 注解結(jié)合 spring aop 自動輸出日志新增攔截器與過濾器[3]
如何動態(tài)修改 spring aop 切面信息?讓自動日志輸出框架更好用[4]
如何將 dubbo filter 攔截器原理運用到日志攔截器中?[5]
v1-基本版本
接口
最常見的定義方式,在方法執(zhí)行前后,異常,finally 提供鉤子函數(shù)。
package com.Github.houbb.auto.log.api;
/**
* autoLog 攔截器
* @author binbin.hou
* @since 0.0.10
*/
public interface IAutoLogInterceptor {
/**
* 執(zhí)行之前
* @param interceptorContext 攔截器上下文
* @since 0.0.10
*/
void beforeHandle(IAutoLogInterceptorContext interceptorContext);
/**
* 執(zhí)行之后
* @param interceptorContext 攔截器上下文
* @param result 方法執(zhí)行結(jié)果
* @since 0.0.10
*/
void afterHandle(IAutoLogInterceptorContext interceptorContext,
final Object result);
/**
* 異常處理
* @param interceptorContext 攔截器上下文
* @param exception 異常
* @since 0.0.10
*/
void exceptionHandle(IAutoLogInterceptorContext interceptorContext, Exception exception);
/**
* finally 中執(zhí)行的代碼
* @param interceptorContext 攔截器上下文
* @since 0.0.10
*/
void finallyHandle(IAutoLogInterceptorContext interceptorContext);
}
工具中統(tǒng)一使用攔截器
package com.github.houbb.auto.log.core.core.impl;
/**
* @author binbin.hou
* @since 0.0.7
*/
public class SimpleAutoLog implements IAutoLog {
/**
* 自動日志輸出
*
* @param context 上下文
* @return 結(jié)果
* @since 0.0.7
*/
@Override
public Object autoLog(IAutoLogContext context) throws Throwable {
//1. 日志唯一標識
// ... 省略
List<IAutoLogInterceptor> autoLogInterceptors = null;
try {
// ... 省略其他邏輯
// 獲取攔截器
autoLogInterceptors = autoLogInterceptors(autoLog);
//1.2 autoLog
if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
interceptor.beforeHandle(autoLogContext);
}
}
//2. 執(zhí)行結(jié)果
Object result = context.process();
//2.1 方法執(zhí)行后
if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
interceptor.afterHandle(autoLogContext, result);
}
}
//2.2 返回方法
return result;
} catch (Exception exception) {
if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
interceptor.exceptionHandle(autoLogContext, exception);
}
}
throw new AutoLogRuntimeException(exception);
} finally {
// 先執(zhí)行日志
if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
interceptor.finallyHandle(autoLogContext);
}
}
}
}
/**
* 創(chuàng)建攔截器列表
* @param autoLog 注解
* @return 結(jié)果
* @since 0.0.10
*/
private List<IAutoLogInterceptor> autoLogInterceptors(final AutoLog autoLog) {
List<IAutoLogInterceptor> resultList = new ArrayList<>();
if(ObjectUtil.isNull(autoLog)) {
return resultList;
}
Class<? extends IAutoLogInterceptor>[] interceptorClasses = autoLog.interceptor();
if(ArrayUtil.isEmpty(interceptorClasses)) {
return resultList;
}
// 循環(huán)創(chuàng)建
for(Class<? extends IAutoLogInterceptor> clazz : interceptorClasses) {
IAutoLogInterceptor traceIdInterceptor = createAutoLogInterceptor(clazz);
resultList.add(traceIdInterceptor);
}
return resultList;
}
/**
* 創(chuàng)建攔截器
* @param clazz 類
* @return 實體
* @since 0.0.10
*/
private IAutoLogInterceptor createAutoLogInterceptor(final Class<? extends IAutoLogInterceptor> clazz) {
if(IAutoLogInterceptor.class.equals(clazz)) {
return new AutoLogInterceptor();
}
return ClassUtil.newInstance(clazz);
}
}
自定義實現(xiàn)攔截器
我們想自定義攔截器方法時,只需要實現(xiàn)對應(yīng)的接口即可。
/**
* 自定義日志攔截器
* @author binbin.hou
* @since 0.0.12
*/
public class MyAutoLogInterceptor extends AbstractAutoLogInterceptor {
@Override
protected void doBefore(AutoLog autoLog, IAutoLogInterceptorContext context) {
System.out.println("自定義入?yún)ⅲ?quot; + Arrays.toString(context.filterParams()));
}
@Override
protected void doAfter(AutoLog autoLog, Object result, IAutoLogInterceptorContext context) {
System.out.println("自定義出參:" + result);
}
@Override
protected void doException(AutoLog autoLog, Exception exception, IAutoLogInterceptorContext context) {
System.out.println("自定義異常:");
exception.printStackTrace();
}
}
方法的不足
這種方式可以實現(xiàn)常見的功能,但是依然不夠優(yōu)雅。
我們還是無法非常靈活的定義自己的攔截器實現(xiàn),就像我們使用 aop 增強,或者 dubbo filter 一樣。
感興趣的小伙伴可以移步學(xué)習(xí)一下,此處不做展開。
Dubbo-02-dubbo invoke filter 鏈式調(diào)用原理[6]
模擬 dubbo filter
實現(xiàn) Invoker
類似 dubbo invoke,直接在以前的類中初始化即可。
AutoLogInvoker autoLogInvoker = new AutoLogInvoker(context);
Invocation invocation = new CommonInvocation();
invocation.setAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_CONTEXT, context);
invocation.setAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_START_TIME, startTimeMills);
invocation.setAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_FILTER_PARAMS, filterParams);
Invoker chAInInvoker = InvokerChainBuilder.buildInvokerChain(autoLogInvoker);
Result autoLogResult = chainInvoker.invoke(invocation);
其中 AutoLogInvoker 只是對方法的執(zhí)行。
實現(xiàn)攔截器
這是的方法增強就是類似 dubbo filter 鏈式調(diào)用實現(xiàn)的,自定義的時候也會方便很多。
不需要拘泥于方法的執(zhí)行位置,直接編寫我們的增強邏輯即可。
package com.github.houbb.auto.log.core.support.interceptor.chain;
import com.alibaba.fastjson.JSON;
import com.github.houbb.auto.log.annotation.AutoLog;
import com.github.houbb.auto.log.api.IAutoLogContext;
import com.github.houbb.auto.log.core.constant.AutoLogAttachmentKeyConst;
import com.github.houbb.common.filter.annotation.FilterActive;
import com.github.houbb.common.filter.api.CommonFilter;
import com.github.houbb.common.filter.api.Invocation;
import com.github.houbb.common.filter.api.Invoker;
import com.github.houbb.common.filter.api.Result;
import com.github.houbb.common.filter.exception.CommonFilterException;
import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.heaven.util.lang.reflect.ClassUtil;
import com.github.houbb.heaven.util.lang.reflect.ReflectMethodUtil;
import com.github.houbb.id.api.Id;
import com.github.houbb.id.core.core.Ids;
import com.github.houbb.id.core.util.IdThreadLocalHelper;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import java.lang.reflect.Method;
/**
* 默認的日志攔截器
*/
@FilterActive(order = Integer.MIN_VALUE)
public class AutoLogCommonFilter implements CommonFilter {
private static final Log LOG = LogFactory.getLog(AutoLogCommonFilter.class);
/**
* 是否需要處理日志自動輸出
* @param autoLog 上下文
* @return 結(jié)果
* @since 0.0.10
*/
protected boolean enableAutoLog(final AutoLog autoLog) {
if(autoLog == null) {
return false;
}
return autoLog.enable();
}
/**
* 獲取方法描述
* @param method 方法
* @param autoLog 注解
* @return 結(jié)果
* @since 0.0.10
*/
protected String getMethodDescription(Method method, AutoLog autoLog) {
String methodName = ReflectMethodUtil.getMethodFullName(method);
if(autoLog != null
&& StringUtil.isNotEmpty(autoLog.description())) {
methodName += "#" + autoLog.description();
}
return methodName;
}
/**
* 獲取 traceId
* @param autoLog 日志注解
* @return 結(jié)果
* @since 0.0.10
*/
protected String getTraceId(AutoLog autoLog) {
//1. 優(yōu)先看當前線程中是否存在
String oldId = IdThreadLocalHelper.get();
if(StringUtil.isNotEmpty(oldId)) {
return formatTraceId(oldId);
}
//2. 返回對應(yīng)的標識
Id id = getActualTraceId(autoLog);
return formatTraceId(id.id());
}
/**
* 獲取日志跟蹤號策略
* @param autoLog 注解
* @return 沒結(jié)果
*/
protected Id getActualTraceId(AutoLog autoLog) {
Class<? extends Id> idClass = autoLog.traceId();
if(Id.class.equals(idClass)) {
return Ids.uuid32();
}
return ClassUtil.newInstance(autoLog.traceId());
}
/**
* 格式化日志跟蹤號
* @param id 跟蹤號
* @return 結(jié)果
* @since 0.0.16
*/
protected String formatTraceId(String id) {
return String.format("[%s] ", id);
}
@Override
public Result invoke(Invoker invoker, Invocation invocation) throws CommonFilterException {
final IAutoLogContext autoLogContext = (IAutoLogContext) invocation.getAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_CONTEXT);
final AutoLog autoLog = autoLogContext.autoLog();
final boolean enableAutoLog = enableAutoLog(autoLog);
if(!enableAutoLog) {
return invoker.invoke(invocation);
}
final String description = getMethodDescription(autoLogContext.method(), autoLog);
// 默認從上下文中取一次
String traceId = IdThreadLocalHelper.get();
try {
// 設(shè)置 traceId 策略
if(autoLog.enableTraceId()) {
Id id = getActualTraceId(autoLog);
traceId = id.id();
invocation.setAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_TRACE_ID, traceId);
IdThreadLocalHelper.put(traceId);
}
Result result = invoker.invoke(invocation);
// 日志增強
logForEnhance(autoLogContext, traceId, description, result.getValue(), invocation);
return result;
} catch (Exception e) {
if (autoLog.exception()) {
String message = String.format("[TID=%s][EXCEPTION=%s]", traceId, e.getMessage());
LOG.error(message, e);
}
throw new RuntimeException(e);
}
}
/**
* 增強日志輸出
* @param autoLogContext 上下文
* @param traceId 日志跟蹤號
* @param description 方法描述
* @param resultValue 返回值
* @param invocation 調(diào)用上下文
*/
private void logForEnhance(final IAutoLogContext autoLogContext,
final String traceId,
final String description,
final Object resultValue,
Invocation invocation) {
final AutoLog autoLog = autoLogContext.autoLog();
StringBuilder logBuilder = new StringBuilder();
logBuilder.Append(String.format("[TID=%s]", traceId));
logBuilder.append(String.format("[METHOD=%s]", description));
// 入?yún)? if(autoLog.param()) {
Object[] params = (Object[]) invocation.getAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_FILTER_PARAMS);
logBuilder.append(String.format("[PARAM=%s]", JSON.toJSONString(params)));
}
// 出參
if (autoLog.result()) {
logBuilder.append(String.format("[RESULT=%s]", JSON.toJSONString(resultValue)));
}
// 耗時
//3.1 耗時 & 慢日志
if(autoLog.costTime()) {
long startTime = (long) invocation.getAttachment(AutoLogAttachmentKeyConst.AUTO_LOG_START_TIME);
long costTime = System.currentTimeMillis() - startTime;
logBuilder.append(String.format("[COST=%d ms]", costTime));
// 慢日志
final long slowThreshold = autoLog.slowThresholdMills();
if(slowThreshold > 0 && costTime > slowThreshold) {
logBuilder.append(String.format("[SLOW-THRESHOLD=%s]", slowThreshold));
}
}
// 輸出日志
LOG.info(logBuilder.toString());
}
}
開源地址
為了便于大家學(xué)習(xí),項目已開源。
Github: https://github.com/houbb/auto-log
Gitee: https://gitee.com/houbinbin/auto-log
小結(jié)
dubbo filter 模式非常的優(yōu)雅,以前一直只是學(xué)習(xí),沒有將其應(yīng)用到自己的項目中。
提供的便利性是非常強大的,值得學(xué)習(xí)運用。
參考資料
auto-log[7]
References
[1] java 注解結(jié)合 spring aop 實現(xiàn)自動輸出日志: https://houbb.github.io/2023/08/06/auto-log-01-overview
[2] java 注解結(jié)合 spring aop 實現(xiàn)日志traceId唯一標識: https://houbb.github.io/2023/08/06/auto-log-02-trace-id
[3] java 注解結(jié)合 spring aop 自動輸出日志新增攔截器與過濾器: https://houbb.github.io/2023/08/06/auto-log-03-filter
[4] 如何動態(tài)修改 spring aop 切面信息?讓自動日志輸出框架更好用: https://houbb.github.io/2023/08/06/auto-log-04-dynamic-aop
[5] 如何將 dubbo filter 攔截器原理運用到日志攔截器中?: https://houbb.github.io/2023/08/06/auto-log-05-dubbo-interceptor
[6] Dubbo-02-dubbo invoke filter 鏈式調(diào)用原理: https://houbb.github.io/2016/09/25/dubbo-02-invoke
[7] auto-log: https://github.com/houbb/auto-log