项目中整合了skywalking 链路监控,skywalking 提供了异常监测功能,那么这个时候我们可以很直观的通过链路立马追踪到出问题的服务以及服务出了什么问题,可以直接在链路上可以查看,假如服务抛异常异常信息会在skywalking的链路节点上显示。
当然这里的异常不仅仅是服务挂掉之类的。
我们先来看上图,图中红线的异常由网关测直接产生,属于网关的本地异常,而黄线部分是在服务侧产生抛回给网关。
没集成skywalking之前,我在每个服务都全局拦截了异常信息,然后封装成消息体正常返回,这样异常被收敛在服务层,skywalking就不知道你服务出现错误了。
当时觉得链路追踪,和异常日志本身侧重点就不一样,所以想,出了问题去查日志呗,于是出了问题一个服务一个服务的去找异常日志,或者在kibana下搜索日志,感觉就是一种大海捞针的行为,明明链路追踪可以立马定位问题的所在,为什么还要去做如此愚蠢的事情呢。
于是把全局异常拦截的代码干掉,让服务层的异常统一抛出去,这个时候新的问题产生了,gateway需要去捕获这个异常统一封装后才能返回给用户。不然就会出现不同的返回格式。
比如出现异常会返回这样
{
"timestamp": 1497850427325,
"status": 500,
"error": "Internal Server Error",
"message": "server error",
"path": "/user"
}
可是我系统统一的返回是这样
{
"code": 0,
"msg": "",
"data": "",
}
好的这个问题可以用如下办法解决
@Component
@Slf4j
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
// -1 is response write filter, must be called before that
return -2;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
HttpHeaders headers = getHeaders();
log.debug("errCode:{}", getHeaders().get(Const.ERROR_CODE));
log.debug("statusCode:{}", getStatusCode());
List<String> errCode = headers.get(Const.ERROR_CODE);
// 如果请求头带有服务抛出的异常统一处理
if (errCode != null && errCode.size() != 0
&& errCode.get(0).equals(String.valueOf(ErrorCode.UNCATCH_EXCEPTION.getCode()))) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
// probably should reuse buffers
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
// 释放掉内存
DataBufferUtils.release(join);
String str = new String(content, Charset.forName("UTF-8"));
log.error("gateway catch exception:{}", str);
JsonResult result = new JsonResult();
result.setCode(ErrorCode.SYS_EXCEPTION.getCode());
result.setMessage(ErrorCode.SYS_EXCEPTION.getMsg());
byte[] newRs = JSONObject.toJSONString(result).getBytes(Charset.forName("UTF-8"));
originalResponse.getHeaders().setContentLength(newRs.length);// 如果不重新设置长度则收不到消息。
return bufferFactory.wrap(newRs);
}));
}
// if body is not a flux. never got there.
return super.writeWith(body);
}
};
// replace response with decorator
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
}
此时,我又想到一个问题,除了200 为成功其它都是失败吗?这好像不一定哦。有可能服务层需要返回其它状态码的错误,那么问题又来了这里不能根据非200 即失败的策略,那么怎么办呢?
当然有办法啦,又想起了全局异常。往头部丢一个异常码。在gateway获取这个异常码后判断是否异常,然后封装否则就直接返回,原本打算使用statuscode 但是这个不知道怎么回事获取不了。
@ExceptionHandler(value = Exception.class)
public void unhandlerException(HttpServletResponse resp, Exception e){
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
e.fillInStackTrace().printStackTrace(printWriter);
resp.addHeader("errCode", "9999");
try {
resp.sendError(9999,result.toString());
} catch (IOException e1) {
//不处理
}
}
这个样就完美了