dubbo的异常处理,全局异常打印堆栈信息

Dubbo全局异常打印堆栈信息问题

问题
  • dubbo的服务提供端的异常返回到消费端时会被拦截,不符合条件的会被转化为字符串,变为RuntimeException异常返回。会导致有全限定名等无用信息。
dubbo官方推荐的异常和日志 链接
  • 尽可能携带完整的上下文信息,比如出错原因,出错的机器地址,调用对方的地址,连的注册中心地址,使用 Dubbo 的版本等。

  • 尽量将直接原因写在最前面,所有上下文信息,在原因后用键值对显示。

  • 抛出异常的地方不用打印日志,由最终处理异常者决定打印日志的级别,吃掉异常必需打印日志。

  • 打印 ERROR 日志表示需要报警,打印 WARN 日志表示可以自动恢复,打印 INFO 表示正常信息或完全不影响运行。

  • 建议应用方在监控中心配置 ERROR 日志实时报警,WARN 日志每周汇总发送通知。

  • RpcException 是 Dubbo 对外的唯一异常类型,所有内部异常,如果要抛出给用户,必须转为 RpcException。

  • RpcException 不能有子类型,所有类型信息用 ErrorCode 标识,以便保持兼容。

dubbo源码异常处理
//Dubbo 版本:2.7.4.1
@Activate(group = Constants.PROVIDER)
public class ExceptionFilter implements Filter {

    private final Logger logger;
    
    public ExceptionFilter() {
        this(LoggerFactory.getLogger(ExceptionFilter.class));
    }
    
    public ExceptionFilter(Logger logger) {
        this.logger = logger;
    }
    
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            Result result = invoker.invoke(invocation);
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    Throwable exception = result.getException();

                    // 如果是checked异常,直接抛出
                    if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {
                        return result;
                    }
                    // 在方法签名上有声明,直接抛出
                    try {
                        Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                        Class<?>[] exceptionClassses = method.getExceptionTypes();
                        for (Class<?> exceptionClass : exceptionClassses) {
                            if (exception.getClass().equals(exceptionClass)) {
                                return result;
                            }
                        }
                    } catch (NoSuchMethodException e) {
                        return result;
                    }

                    // 未在方法签名上定义的异常,在服务器端打印ERROR日志
                    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);

                    // 异常类和接口类在同一jar包里,直接抛出
                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
                        return result;
                    }
                    // 是JDK自带的异常,直接抛出
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        return result;
                    }
                    // 是Dubbo本身的异常,直接抛出
                    if (exception instanceof RpcException) {
                        return result;
                    }

                    // 否则,包装成RuntimeException抛给客户端
                    return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
                } catch (Throwable e) {
                    logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                            + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
                    return result;
                }
            }
            return result;
        } catch (RuntimeException e) {
            logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                    + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                    + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
            throw e;
        }
    }
}
放行条件
  1. 如果provider实现了GenericService接口,直接抛出

  2. 如果是checked异常,直接抛出

  3. 在方法签名上有声明,直接抛出

  4. 异常类和接口类在同一jar包里,直接抛出

  5. 是JDK自带的异常,直接抛出

  6. 是Dubbo本身的异常,直接抛出

  7. 否则,包装成RuntimeException抛给客户端

处理方法
  • 将该异常的包名以"java.或者"javax. " 开头

  • 使用受检异常(继承Exception)

  • 不用异常,使用错误码

  • 把异常放到provider-api的jar包中

  • 判断异常message是否以XxxException.class.getName()开头(其中XxxException是自定义的业务异常)

  • provider实现GenericService接口

  • provider的api明确写明throws XxxException,发布provider(其中XxxException是自定义的业务异常)

  • 实现dubbo的filter,自定义provider的异常处理逻辑

  • 除了上面对应的,还可以用一种奇葩的方式,直接去掉异常的filter,如下(会有bug): <dubbo:provider filter="-exception" />

  • 推荐的方法是修改源码放行条件,但是修改源码jar包导致以后只能用这个jar包所以,复制源码,新建xxxFilter.java异常处理类,在条件中加入if(className.startWith(“自定义异常路径上级.”)) return; 然后SPI方式指定自定义xxxFilter,并排除dubbo本身Filter;详情链接

参考:链接链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值