最近完成一個需求,使用阿裡Arms需要在log裡面加上traceId,但是發現dubbo異常 被ExceptionFilter捕獲 并列印 列印不出traceI,然後百度搜尋如何重寫Filter
參考了這篇文章
https://www.jianshu.com/p/7e7076212bd0
重寫ExceptionFilter
1.新增一個DubboExceptionFilter類
标紅部分 是我改動電腦 其他都是複制原來的ExceptionFilter
@Activate(
group = {"provider"}
)
public class ArmsDubboExceptionFilter implements Filter {
private final Logger logger;
public ArmsDubboExceptionFilter() {
this(LoggerFactory.getLogger(ArmsDubboExceptionFilter.class));
}
public ArmsDubboExceptionFilter(Logger logger) {
this.logger = logger;
}
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
setSpan(invocation);
Result result = invoker.invoke(invocation);
if (result.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = result.getException();
if (!(exception instanceof RuntimeException) && exception instanceof Exception) {
return result;
} else {
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
Class[] arr$ = exceptionClassses;
int len$ = exceptionClassses.length;
for (int i$ = 0; i$ < len$; ++i$) {
Class<?> exceptionClass = arr$[i$];
if (exception.getClass().equals(exceptionClass)) {
return result;
}
}
} catch (NoSuchMethodException var11) {
return result;
}
this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile != null && exceptionFile != null && !serviceFile.equals(exceptionFile)) {
String className = exception.getClass().getName();
if (!className.startsWith("java.") && !className.startsWith("javax.")) {
return (Result) (exception instanceof RpcException ? result : new RpcResult(new RuntimeException(StringUtils.toString(exception))));
} else {
return result;
}
} else {
return result;
}
}
} catch (Throwable var12) {
this.logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var12.getClass().getName() + ": " + var12.getMessage(), var12);
return result;
}
} else {
return result;
}
} catch (RuntimeException var13) {
this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var13.getClass().getName() + ": " + var13.getMessage(), var13);
throw var13;
} finally {
ArmsUtils.remove();
}
}
/*
* 因為通過arms sdk拿不到traceId(與阿裡工程師溝通 貌似是bug 暫時自己再協定頭裡面擷取 并存入線程緩存 供log appender使用)
* @param invocation
*/
public void setSpan(Invocation invocation) {
try {
String sampled = invocation.getAttachment("X-B3-Sampled");
ArmsUtils.setSpan(invocation.getAttachment("X-B3-TraceId"), invocation.getAttachment("EagleEye-RpcID"), invocation.getAttachment("X-B3-SpanId"), sampled != null && sampled.equals("1"));
} catch (Exception e) {
logger.error("寫入span異常", e);
}
}
}
2.在/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter新增一個檔案com.alibaba.dubbo.rpc.Filter
内容
DubboExceptionFilter=com.biz.core.armslog.ArmsDubboExceptionFilter
3.soa-provider.xml配置
<!-- 延遲暴露服務,表示延遲到Spring容器初始化完成時暴露服務; 不重試 filter自定義一個dubboExceptionFilter -exception表示替換了預設的ExceptionFilter 增加arms日志列印-->
<dubbo:provider delay="-1" retries="0" filter="DubboExceptionFilter,-exception"/>
ProtocolFilterWrapper調用時機
這裡用到dubboSPI參見《dubbo源碼閱讀-dubbo SPI實作原理(五)》 《 ProtocolFilterWrapper調用時機》
閱讀源碼 了解為什麼這麼寫
ProtocolFilterWrapper
為dubboFilter的包裝類 用來為生成filter執行鍊
/**
* 為invoker生成filter調用鍊條 invoker為執行對象
* @param invoker 執行對象
* @param key 為參數名
* @param group 等于@Activate(group = {"provider"})
* @param <T>
* @return
*/
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
final Invoker<T> last = invoker;
/**
* 使用dubbo SPI擷取預設和使用者自定義的filter
* url為暴露的服務位址
* keyinjvm://127.0.0.1/com.biz.soa.service.promotion.backend.OfflineExtend.PromotionOfflineService?anyhost=true&application=soa-promotion-provider&bind.ip=10.37.129.2&bind.port=23888&default.delay=-1&default.retries=0&default.service.filter=DubboExceptionFilter,-exception&delay=-1&dispatcher=message&dubbo=2.6.2&generic=false&interface=com.biz.soa.service.promotion.backend.OfflineExtend.PromotionOfflineService&methods=downLoadPromotion,updaPromotion,queryPromotionOffline,getOfflineProducts,SavePromotion&pid=81391®ister=false&side=provider&threadpool=fixed&threads=500×tamp=1574842487417
* group為provider (如果是consumer則是consumer)
* */
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
/**
* 這裡是循環生成filer調用鍊條 filter1->filter2->invoke
*/
for(int i = filters.size() - 1; i >= 0; --i) {
final Filter filter = (Filter)filters.get(i);
last = new Invoker<T>() {
public Class<T> getInterface() {
return invoker.getInterface();
}
public URL getUrl() {
return invoker.getUrl();
}
public boolean isAvailable() {
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(last, invocation);
}
public void destroy() {
invoker.destroy();
}
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
ExtensionLoader
ExtensionLoader.getExtensionLoader實作
/**
* 根據class擷取對應的ExtensionLoader
* @param type
* @param <T>
* @return
*/
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
} else if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
} else if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
} else {
//因為是泛型對應的擴充點 提前初始化了一個對應類型的 ExtensionLoader到 這裡就會擷取到ExtensionLoader<Filter> 的對象
ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
}
return loader;
}
}
ExtensionLoader.getActivateExtension
/**
* 擷取所有filter 預設的或者使用者配置的
* @param url
* @param values
* @param group
* @return
*/
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList();
List<String> names = values == null ? new ArrayList(0) : Arrays.asList(values);
String name;
/**
* 這裡是加載預設的filter 如果配置了 -default 将忽略所有預設過濾器
*/
if (!((List)names).contains("-default")) {
this.getExtensionClasses();
Iterator i$ = this.cachedActivates.entrySet().iterator();
while(i$.hasNext()) {
Map.Entry<String, Activate> entry = (Map.Entry)i$.next();
/**
* 獲得spi配置的key名字 如dubboSPI配置: exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
*/
name = (String)entry.getKey();
Activate activate = (Activate)entry.getValue();
/**
* 這裡比對Activate的配置的group 隻找出provider的
*/
if (this.isMatchGroup(group, activate.group())) {
T ext = this.getExtension(name);
/**
* 因為我們配置了-exception 是以這裡不會加載
* isActive如果注解配置了value@Activate(group={"provider"},value={"token"}) 則會在parameter檢查是否有傳遞token 參數 如果有才加入到過濾器
*/
if (!((List)names).contains(name) && !((List)names).contains("-" + name) && this.isActive(activate, url)) {
exts.add(ext);
}
}
}
/**
* 排序 可以看ActivateCompartor.COMPARATOR實作 這個排序實作是根據 注解的order 值來排的
*/
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
/**
* 下面是加載使用者的Filter
* <dubbo:provider delay="-1" retries="0" filter="DubboExceptionFilter,-exception"/>
*/
List<T> usrs = new ArrayList();
for(int i = 0; i < ((List)names).size(); ++i) {
name = (String)((List)names).get(i);
//-開頭的的表示剔除 不執行邏輯
if (!name.startsWith("-") && !((List)names).contains("-" + name)) {
if ("default".equals(name)) {
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
T ext = this.getExtension(name);
usrs.add(ext);
}
}
}
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}
private boolean isActive(Activate activate, URL url) {
//獲得注解桑的value
String[] keys = activate.value();
//如果沒有值直接傳回true
if (keys.length == 0) {
return true;
} else {
String[] arr$ = keys;
int len$ = keys.length;
label34:
//周遊values
for(int i$ = 0; i$ < len$; ++i$) {
String key = arr$[i$];
//獲得參數清單疊代器 參數清單為xml配置哦
Iterator i$ = url.getParameters().entrySet().iterator();
String k;
String v;
do {
do {
if (!i$.hasNext()) {
continue label34;
}
Map.Entry<String, String> entry = (Map.Entry)i$.next();
k = (String)entry.getKey();
v = (String)entry.getValue();
//參數包含了 過濾器資訊 同時值不能為空 就加入過濾器
} while(!k.equals(key) && !k.endsWith("." + key));
} while(!ConfigUtils.isNotEmpty(v));
return true;
}
return false;
}
}
看完之後是不是一切都明朗了。。我們的com.alibaba.dubbo.rpc.Filter 配置 是SPI實作方式 實作動态注入