微服務網關實戰03-網關請求日誌及全局異常

在上一篇文章中,我們介紹了採用zuul搭建一個網關,做了一個最簡單的調用測試,正常是可以訪問的。在這一章中,我們介紹網關的日誌及全局異常。

日誌記錄了誰通過網關,做了什麼事。

全局異常攔截那些發生問題的不可見的異常。

微服務網關實戰03-網關請求日誌及全局異常

 

在開始之前,我們需要給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,注意升級到最新版。

整體的項目結構如下:

微服務網關實戰03-網關請求日誌及全局異常

項目結構

添加日誌請求

這裏的日誌請求,網上一般的做法都是把請求前的日誌與請求後的返回日誌放在一起,用到了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("發生全局異常");
    }

至此,日誌記錄與全局異常做完,我們來測試一下效果。

測試日誌請求:還是上一章節的請求,直接調用,結果如下

微服務網關實戰03-網關請求日誌及全局異常

日誌請求結果

可以看到,日誌已經生效了,接下來,我們來測試一下全局異常

備註:這裏需要手動寫個異常,直接在LogsPreFilter的shouldFilter方法拋出異常即可

微服務網關實戰03-網關請求日誌及全局異常

製造異常

測試全局異常:還是繼續訪問剛剛的接口

微服務網關實戰03-網關請求日誌及全局異常

全局異常返回

微服務網關實戰03-網關請求日誌及全局異常

全局異常後臺

從這兩個截圖上看,全局異常正常返回,後臺也能輸出異常的問題。整合完成。

最後,我們做個總結:zuul的日誌請求與全局異常,主要還是利用了ZuulFilter來做,這個東西會經常用,下一章節我們的限流,斷路器也會使用到他。

最後,謝謝觀賞,覺得好的話,點個贊,有什麼問題可以留言溝通,麼麼噠。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章