項目中整合了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) {
//不處理
}
}
這個樣就完美了