在上一篇文章中,我們介紹了採用zuul搭建一個網關,做了一個最簡單的調用測試,正常是可以訪問的。在這一章中,我們介紹網關的日誌及全局異常。
日誌記錄了誰通過網關,做了什麼事。
全局異常攔截那些發生問題的不可見的異常。
在開始之前,我們需要給pom文件裏面新增兩個包,lambok及fastjson
<!--swagger2工具 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.8.8</version>
</dependency>
<!-- lombok支持 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 阿里json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.66</version>
</dependency>
備註:很多人喜歡用fastjson,最近fastjson爆出了兩次服務器級別的bug,注意升級到最新版。
整體的項目結構如下:
項目結構
添加日誌請求
這裏的日誌請求,網上一般的做法都是把請求前的日誌與請求後的返回日誌放在一起,用到了ZuulFilter裏面filterType爲POST_TYPE,這裏我們分開來做,兩個日誌過濾請求器,一個用於請求前,一個用於請求後數據的數據返回,代碼如下
請求前日誌類LogsPreFilter:
/**
* All rights Reserved, Designed By OprCalf
* Copyright: Copyright(C) 2016-2020
* Company LengYin Ltd.
*/
package com.platform.gateway.filter;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
/**
* @projectName: platform-gateway-demo
* @package: com.platform.gateway.common.filters
* @className: LogsFilter.java
* @description: 後置日誌記錄
* @author: OprCalf
* @date: 2020年3月4日
*/
@Slf4j
@Component
public class LogsPostFilter extends ZuulFilter {
@Override
public String filterType() {
// 要打印返回信息,必須得用"post"
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
return Integer.MIN_VALUE + 6;
}
@Override
public boolean shouldFilter() {
final RequestContext ctx = RequestContext.getCurrentContext();
// 當發生異常的時候,不是進行取值
if (ctx.getThrowable() != null) {
log.error("{}", ctx.getThrowable().fillInStackTrace());
if (ctx.sendZuulResponse()) {
return true;
} else {
return false;
}
} else {
return true;
}
}
@Override
public Object run() {
try {
// 獲取當前請求的對象
final RequestContext ctx = RequestContext.getCurrentContext();
// 打印返回的response信息
final InputStream out = ctx.getResponseDataStream();
// 獲取返回的body裏面的值
final String outBody = StreamUtils.copyToString(out, Charset.forName("UTF-8"));
// 當進入了限流的時候,此時返回的body裏面的值爲空,
log.info("返回值:{}", outBody);
// 把返回的body的值重新設置回去,前臺才能拿到
ctx.setResponseBody(outBody);
}
catch (final Exception e) {
log.error("LogsFilter發生異常:{}", e);
}
return null;
}
}
請求後日志類LogsPostFilter:
/**
* All rights Reserved, Designed By OprCalf
* Copyright: Copyright(C) 2016-2020
* Company LengYin Ltd.
*/
package com.platform.gateway.filter;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
/**
* @projectName: platform-gateway-demo
* @package: com.platform.gateway.common.filters
* @className: LogsFilter.java
* @description: 後置日誌記錄
* @author: OprCalf
* @date: 2020年3月4日
*/
@Slf4j
@Component
public class LogsPostFilter extends ZuulFilter {
@Override
public String filterType() {
// 要打印返回信息,必須得用"post"
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
return Integer.MIN_VALUE + 6;
}
@Override
public boolean shouldFilter() {
final RequestContext ctx = RequestContext.getCurrentContext();
// 當發生異常的時候,不是進行取值
if (ctx.getThrowable() != null) {
log.error("{}", ctx.getThrowable().fillInStackTrace());
if (ctx.sendZuulResponse()) {
return true;
} else {
return false;
}
} else {
return true;
}
}
@Override
public Object run() {
try {
// 獲取當前請求的對象
final RequestContext ctx = RequestContext.getCurrentContext();
// 打印返回的response信息
final InputStream out = ctx.getResponseDataStream();
// 獲取返回的body裏面的值
final String outBody = StreamUtils.copyToString(out, Charset.forName("UTF-8"));
// 當進入了限流的時候,此時返回的body裏面的值爲空,
log.info("返回值:{}", outBody);
// 把返回的body的值重新設置回去,前臺才能拿到
ctx.setResponseBody(outBody);
}
catch (final Exception e) {
log.error("LogsFilter發生異常:{}", e);
}
return null;
}
}
全局異常類:ErrorFilter
/**
* All rights Reserved, Designed By OprCalf
* Copyright: Copyright(C) 2016-2020
* Company LengYin Ltd.
*/
package com.platform.gateway.filter;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
/**
* @projectName: platform-gateway-demo
* @package: com.platform.gateway.common.filters
* @className: ErrorFilter.java
* @description: 全局異常
* @author: OprCalf
* @date: 2020年3月5日
*/
@Slf4j
@Component
public class ErrorFilter extends ZuulFilter {
protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
@Value("${error.path:/gobleError}")
private String errorPath;
@Override
public String filterType() {
return FilterConstants.ERROR_TYPE;
}
@Override
public int filterOrder() {
return Integer.MIN_VALUE;
}
@Override
public boolean shouldFilter() {
final RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getThrowable() != null) {
log.error("{}", ctx.getThrowable().fillInStackTrace());
if (ctx.sendZuulResponse()) {
return true;
} else {
return false;
}
} else {
return false;
}
}
@Override
public Object run() {
try {
final RequestContext ctx = RequestContext.getCurrentContext();
final ZuulException exception = findZuulException(ctx.getThrowable());
final HttpServletRequest request = ctx.getRequest();
request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode);
log.warn("Error during filtering", exception);
request.setAttribute("javax.servlet.error.exception", exception);
if (StringUtils.hasText(exception.errorCause)) {
request.setAttribute("javax.servlet.error.message", exception.errorCause);
}
final RequestDispatcher dispatcher = request.getRequestDispatcher(errorPath);
if (dispatcher != null) {
ctx.set(SEND_ERROR_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
ctx.setResponseStatusCode(exception.nStatusCode);
dispatcher.forward(request, ctx.getResponse());
}
}
}
catch (final Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
ZuulException findZuulException(Throwable throwable) {
if (throwable.getCause() instanceof ZuulRuntimeException) {
return (ZuulException) throwable.getCause().getCause();
}
if (throwable.getCause() instanceof ZuulException) {
return (ZuulException) throwable.getCause();
}
if (throwable instanceof ZuulException) {
return (ZuulException) throwable;
}
return new ZuulException(throwable, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
}
public void setErrorPath(String errorPath) {
this.errorPath = errorPath;
}
}
在這個全局的異常類中,我們定義了在shouldFilter中,當發生異常的時候,記錄下這個異常,並繼續往下執行,此時就會執行run方法
在run方法中,我們定義了一個全局的錯誤頁,當發生異常的時候,會直接跳轉到這個錯誤頁,最後一步就是寫一個錯誤頁的方法。
@SuppressWarnings("deprecation")
@ApiOperation(value = "獲取測試信息")
@GetMapping(value = "/gobleError", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseMsg<String> gobleError(HttpServletRequest request) {
return MsgUtils.buildFailureMsg("發生全局異常");
}
至此,日誌記錄與全局異常做完,我們來測試一下效果。
測試日誌請求:還是上一章節的請求,直接調用,結果如下
日誌請求結果
可以看到,日誌已經生效了,接下來,我們來測試一下全局異常
備註:這裏需要手動寫個異常,直接在LogsPreFilter的shouldFilter方法拋出異常即可
製造異常
測試全局異常:還是繼續訪問剛剛的接口
全局異常返回
全局異常後臺
從這兩個截圖上看,全局異常正常返回,後臺也能輸出異常的問題。整合完成。
最後,我們做個總結:zuul的日誌請求與全局異常,主要還是利用了ZuulFilter來做,這個東西會經常用,下一章節我們的限流,斷路器也會使用到他。
最後,謝謝觀賞,覺得好的話,點個贊,有什麼問題可以留言溝通,麼麼噠。