业务背景

我们希望可以在使用日志拦截器时,定义属于自己的拦截器方法。

实现的方式有很多种,我们分别来看一下。

拓展阅读

java 注解结合 spring aop 实现自动输出日志

java 注解结合 spring aop 实现日志traceId唯一标识

java 注解结合 spring aop 自动输出日志新增拦截器与过滤器

如何动态修改 spring aop 切面信息?让自动日志输出框架更好用

如何将 dubbo filter 拦截器原理运用到日志拦截器中?

v1-基本版本

接口

最常见的定义方式,在方法执行前后,异常,finally 提供钩子函数。

package com.github.houbb.auto.log.api;

/**
* autoLog 拦截器
* @author binbin.hou
* @since 0.0.10
*/
public interface IAutoLogInterceptor { /**
* 执行之前
* @param interceptorContext 拦截器上下文
* @since 0.0.10
*/
void beforeHandle(IAutoLogInterceptorContext interceptorContext); /**
* 执行之后
* @param interceptorContext 拦截器上下文
* @param result 方法执行结果
* @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 中执行的代码
* @param interceptorContext 拦截器上下文
* @since 0.0.10
*/
void finallyHandle(IAutoLogInterceptorContext interceptorContext); }

工具中统一使用拦截器

package com.github.houbb.auto.log.core.core.impl;
/**
* @author binbin.hou
* @since 0.0.7
*/
public class SimpleAutoLog implements IAutoLog { /**
* 自动日志输出
*
* @param context 上下文
* @return 结果
* @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. 执行结果
Object result = context.process(); //2.1 方法执行后
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 {
// 先执行日志
if(CollectionUtil.isNotEmpty(autoLogInterceptors)) {
for(IAutoLogInterceptor interceptor : autoLogInterceptors) {
interceptor.finallyHandle(autoLogContext);
}
}
}
} /**
* 创建拦截器列表
* @param autoLog 注解
* @return 结果
* @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;
} // 循环创建
for(Class<? extends IAutoLogInterceptor> clazz : interceptorClasses) {
IAutoLogInterceptor traceIdInterceptor = createAutoLogInterceptor(clazz);
resultList.add(traceIdInterceptor);
} return resultList;
} /**
* 创建拦截器
* @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);
} }

自定义实现拦截器

我们想自定义拦截器方法时,只需要实现对应的接口即可。

/**
* 自定义日志拦截器
* @author binbin.hou
* @since 0.0.12
*/
public class MyAutoLogInterceptor extends AbstractAutoLogInterceptor { @Override
protected void doBefore(AutoLog autoLog, IAutoLogInterceptorContext context) {
System.out.println("自定义入参:" + 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();
} }

方法的不足

这种方式可以实现常见的功能,但是依然不够优雅。

我们还是无法非常灵活的定义自己的拦截器实现,就像我们使用 aop 增强,或者 dubbo filter 一样。

感兴趣的小伙伴可以移步学习一下,此处不做展开。

Dubbo-02-dubbo invoke filter 链式调用原理

模拟 dubbo filter

实现 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 只是对方法的执行。

实现拦截器

这是的方法增强就是类似 dubbo filter 链式调用实现的,自定义的时候也会方便很多。

不需要拘泥于方法的执行位置,直接编写我们的增强逻辑即可。

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 结果
* @since 0.0.10
*/
protected boolean enableAutoLog(final AutoLog autoLog) {
if(autoLog == null) {
return false;
} return autoLog.enable();
} /**
* 获取方法描述
* @param method 方法
* @param autoLog 注解
* @return 结果
* @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 结果
* @since 0.0.10
*/
protected String getTraceId(AutoLog autoLog) {
//1. 优先看当前线程中是否存在
String oldId = IdThreadLocalHelper.get();
if(StringUtil.isNotEmpty(oldId)) {
return formatTraceId(oldId);
} //2. 返回对应的标识
Id id = getActualTraceId(autoLog);
return formatTraceId(id.id());
} /**
* 获取日志跟踪号策略
* @param autoLog 注解
* @return 没结果
*/
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 结果
* @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 {
// 设置 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 调用上下文
*/
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)); // 入参
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());
} }

开源地址

为了便于大家学习,项目已开源。

Github: https://github.com/houbb/auto-log

Gitee: https://gitee.com/houbinbin/auto-log

小结

dubbo filter 模式非常的优雅,以前一直只是学习,没有将其应用到自己的项目中。

提供的便利性是非常强大的,值得学习运用。

参考资料

auto-log

如何将 dubbo filter 拦截器原理运用到日志拦截器中?的更多相关文章

  1. Struts2拦截器原理以及实例

    一.Struts2拦截器定义 1. Struts2拦截器是在访问某个Action或Action的某个方法,字段之前或之后实施拦截,并且Struts2拦截器是可插拔的,拦截器是AOP的一种实现. 2. ...

  2. 为什么我们需要Logstash,Fluentd等日志摄取器?

    前文传送门:Logging with ElasticSearch, Kibana, ASP.NET Core and Docker 疑问:既然应用能直接向ElasticSearch写日志,为什么我们还 ...

  3. kbmMWLog同时输出日志到多个日志管理器

    kbmMWLog日志框架,针对不同的业务情况,提供了多种日志管理器: TkbmMWStreamLogManager TkbmMWLocalFileLogManager TkbmMWSystemLogM ...

  4. KBMMW 的日志管理器

    kbmmw 4.82 最大的新特性就是增加了 日志管理器. 新的日志管理器实现了不同类型的日志.断言.异常处理.计时等功能. 首先.引用kbmMWLog.pas 单元后,系统就默认生成一个IkbmMW ...

  5. struts2 javaweb 过滤器、监听器 拦截器 原理

    转: 过滤器.监听器 拦截器 过滤器 创建一个 Filter 只需两个步骤: (1)创建 Filter 处理类: (2)在 web.xml 文件中配置 Filter . 创建 Filter 必须实现 ...

  6. Dubbo自定义日志拦截器

    前言 上一篇文章 Spring aop+自定义注解统一记录用户行为日志 记录了 web层中通过自定义注解配合Spring aop自动记录用户行为日志的过程.那么按照分布式架构中Dubbo服务层的调用过 ...

  7. 【转】 AOP(面向切面编程)、Filter(过虑器)、Interceptor(拦截器)

    AOP(面向切面编程) 面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承.多态和封装.而封装就要求将功能分散到不同的对象中去,这在软 ...

  8. JavaWeb过滤器.监听器.拦截器-原理&区别-个人总结

    对比项 拦截器 过滤器 机制 反射机制 函数回调 是否依赖servlet容器 是 否 请求处理 只能对action请求起作用 几乎所有的请求起作用 对action处理 可以访问action上下文.值栈 ...

  9. JavaWeb过滤器.监听器.拦截器-原理&区别(转)

    1.拦截器是基于java的反射机制的,而过滤器是基于函数回调 2.过滤器依赖与servlet容器,而拦截器不依赖与servlet容器 3.拦截器只能对action请求起作用,而过滤器则可以对几乎所有的 ...

  10. spring mvc拦截器原理分析

    我的springMVC+mybatis中的interceptor使用@autowired注入DAO失败,导致报空指针错误,这个是为什么呢? :空指针说明没有注入进来,你可以检查一下你的这个拦截器int ...

随机推荐

  1. #Powerbi 利用EOMONTH,计算月度时间进度

    在POWERBI中计算本月的时间进度 许多小伙伴在做看板时,往往会遇到需要时间进度的计算,来配合业绩达成的进度,进行比较分析.那么如何在BI做通过公式进行这个数据的求值呢? 首先,我们需要测算两个值, ...

  2. 2022-12-10:给你一个由小写字母组成的字符串 s ,和一个整数 k 如果满足下述条件,则可以将字符串 t 视作是 理想字符串 : t 是字符串 s 的一个子序列。 t 中每两个 相邻 字母在字

    2022-12-10:给你一个由小写字母组成的字符串 s ,和一个整数 k 如果满足下述条件,则可以将字符串 t 视作是 理想字符串 : t 是字符串 s 的一个子序列. t 中每两个 相邻 字母在字 ...

  3. 2022-12-02:有a块草莓蛋糕,有b块芝士蛋糕,两人轮流拿蛋糕, 每次不管是谁只能选择在草莓蛋糕和芝士蛋糕中拿一种, 拿的数量在1~m之间随意, 谁先拿完最后的蛋糕谁赢。 返回先手赢还是后手赢。

    2022-12-02:有a块草莓蛋糕,有b块芝士蛋糕,两人轮流拿蛋糕, 每次不管是谁只能选择在草莓蛋糕和芝士蛋糕中拿一种, 拿的数量在1~m之间随意, 谁先拿完最后的蛋糕谁赢. 返回先手赢还是后手赢. ...

  4. 2021-08-30:给定两个字符串str1和str2,在str1中寻找一个最短子串,能包含str2的所有字符,字符顺序无所谓,str1的这个最短子串也可以包含多余的字符。返回这个最短包含子串。

    2021-08-30:给定两个字符串str1和str2,在str1中寻找一个最短子串,能包含str2的所有字符,字符顺序无所谓,str1的这个最短子串也可以包含多余的字符.返回这个最短包含子串. 福大 ...

  5. Module not found: Error: Can't resolve 'axios' in 'D:\BaiduSyncdisk\vue-cli-project\dc_vue3\src\utils'

    Module not found: Error: Can't resolve 'axios' in 'D:\BaiduSyncdisk\vue-cli-project\dc_vue3\src\util ...

  6. js通过className删除元素

    有时候难免需要使用js进行 dom 操作:如在删除地图feature时同时得清除提示框 这个就需要使用 .parentNode.removeChild(元素) let chArr = document ...

  7. 解决echarts图形由于label过长导致文字显示不全问题

    使用echarts 打印饼图,在pc没问题,但一到移动端问题就来了,由于屏幕过小,导致label部分被遮挡 一.问题分析 如上图这个就尴尬了,囧么办呢? 还好echarts 提供了formatter方 ...

  8. kali系统安装redis步骤

    环境: 攻击机:Kali  5.16.0-kali7-amd64    192.168.13.78 靶机:   Kali  5.16.0-kali7-amd64    192.168.13.94 安装 ...

  9. # 代码随想录算法训练营Day31 贪心算法| 1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果

    代码随想录算法训练营 1005.K次取反后最大化的数组和 题目链接:1005.K次取反后最大化的数组和 给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 ...

  10. 2021-i春秋秋季赛部分misc+re wp

    两道re比较简单,没有什么好说的,misc的符号执行那题还有点意思,总的来说题目不难 CTFMakesMeAngr 给出了符号执行的中间语言,对着文档把汇编逻辑逆一下,发现是个凯撒加密,位移为14,注 ...