在上一篇文章中,我们介绍了采用zuul搭建一个网关,做了一个最简单的调用测试,正常是可以访问的。在这一章中,我们介绍网关的日志及全局异常。
日志记录了谁通过网关,做了什么事。
全局异常拦截那些发生问题的不可见的异常。
在开始之前,我们需要给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,注意升级到最新版。
整体的项目结构如下:
项目结构
这里的日志请求,网上一般的做法都是把请求前的日志与请求后的返回日志放在一起,用到了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("发生全局异常");
}
至此,日志记录与全局异常做完,我们来测试一下效果。
测试日志请求:还是上一章节的请求,直接调用,结果如下
日志请求结果
可以看到,日志已经生效了,接下来,我们来测试一下全局异常
备注:这里需要手动写个异常,直接在LogsPreFilter的shouldFilter方法抛出异常即可
制造异常
测试全局异常:还是继续访问刚刚的接口
全局异常返回
全局异常后台
从这两个截图上看,全局异常正常返回,后台也能输出异常的问题。整合完成。
最后,我们做个总结:zuul的日志请求与全局异常,主要还是利用了ZuulFilter来做,这个东西会经常用,下一章节我们的限流,断路器也会使用到他。
最后,谢谢观赏,觉得好的话,点个赞,有什么问题可以留言沟通,么么哒。