微服务网关实战03-网关请求日志及全局异常

在上一篇文章中,我们介绍了采用zuul搭建一个网关,做了一个最简单的调用测试,正常是可以访问的。在这一章中,我们介绍网关的日志及全局异常。

日志记录了谁通过网关,做了什么事。

全局异常拦截那些发生问题的不可见的异常。

微服务网关实战03-网关请求日志及全局异常_第1张图片

 

在开始之前,我们需要给pom文件里面新增两个包,lambok及fastjson


        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            com.github.xiaoymin
            swagger-bootstrap-ui
            1.8.8
        


        
            org.projectlombok
            lombok
        
        
        
        
            com.alibaba
            fastjson
            1.2.66
        

备注:很多人喜欢用fastjson,最近fastjson爆出了两次服务器级别的bug,注意升级到最新版。

整体的项目结构如下:

微服务网关实战03-网关请求日志及全局异常_第2张图片

项目结构

添加日志请求

这里的日志请求,网上一般的做法都是把请求前的日志与请求后的返回日志放在一起,用到了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 gobleError(HttpServletRequest request) {
        return MsgUtils.buildFailureMsg("发生全局异常");
    }

至此,日志记录与全局异常做完,我们来测试一下效果。

测试日志请求:还是上一章节的请求,直接调用,结果如下

微服务网关实战03-网关请求日志及全局异常

日志请求结果

可以看到,日志已经生效了,接下来,我们来测试一下全局异常

备注:这里需要手动写个异常,直接在LogsPreFilter的shouldFilter方法抛出异常即可

微服务网关实战03-网关请求日志及全局异常_第3张图片

制造异常

测试全局异常:还是继续访问刚刚的接口

微服务网关实战03-网关请求日志及全局异常_第4张图片

全局异常返回

微服务网关实战03-网关请求日志及全局异常_第5张图片

全局异常后台

从这两个截图上看,全局异常正常返回,后台也能输出异常的问题。整合完成。

最后,我们做个总结:zuul的日志请求与全局异常,主要还是利用了ZuulFilter来做,这个东西会经常用,下一章节我们的限流,断路器也会使用到他。

最后,谢谢观赏,觉得好的话,点个赞,有什么问题可以留言沟通,么么哒。

你可能感兴趣的:(微服务网关)