Skip to content

Commit

Permalink
feature: add api log && global exception for gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
luckyQing committed Jul 26, 2020
1 parent 4c39194 commit 5ada67d
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.smartframework.cloud.examples.support.gateway.configure;

import lombok.extern.slf4j.Slf4j;
import org.smartframework.cloud.common.pojo.Base;
import org.smartframework.cloud.common.pojo.vo.RespVO;
import org.smartframework.cloud.examples.support.gateway.filter.log.ApiLogDO;
import org.smartframework.cloud.examples.support.gateway.filter.log.LogUtil;
import org.smartframework.cloud.starter.web.exception.ExceptionHandlerContext;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
* 全局异常处理
*
* @author liyulin
* @date 2020-07-21
*/
@Slf4j
@Configuration
public class GatewayErrorWebExceptionHandler implements ErrorWebExceptionHandler {

@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
log.error("gateway.errorlog", throwable);
RespVO<Base> respVO = new RespVO<>(ExceptionHandlerContext.transRespHead(throwable));
String response = respVO.toString();
printErrorLog(response);

ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
return serverHttpResponse.writeWith(Mono.fromSupplier(() -> {
return serverHttpResponse.bufferFactory().wrap(response.getBytes(StandardCharsets.UTF_8));
}));
}

/**
* 错误请求日志打印
*
* @param response
*/
private void printErrorLog(String response) {
ApiLogDO apiLogDO = LogUtil.getApiLogCache().get();
if (apiLogDO != null) {
apiLogDO.setCost(System.currentTimeMillis() - apiLogDO.getCost());
apiLogDO.setResult(response);
log.info("gateway.log.error=>{}", apiLogDO);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.smartframework.cloud.examples.support.gateway.constants;

import org.springframework.core.Ordered;

/**
* order
*
* @author liyulin
* @date 2020-07-17
*/
public interface Order {

/**
* 请求日志order
*/
int REQUEST_LOG = Ordered.HIGHEST_PRECEDENCE;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.smartframework.cloud.examples.support.gateway.filter.log;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.*;

import java.io.Serializable;

/**
* @author liyulin
* @date 2020-07-17
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiLogDO implements Serializable {

/**
* 请求路径
*/
private String url;
/**
* http请求方式
*/
private String method;
/**
* http头部数据
*/
private String head;
/**
* url参数
*/
private String queryParams;
/**
* body部分请求体参数
*/
private Object args;
/**
* 请求结果
*/
private String result;
/**
* 花费时间(毫秒)
*/
private long cost;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.smartframework.cloud.examples.support.gateway.filter.log;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.smartframework.cloud.examples.support.gateway.constants.Order;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SignalType;

/**
* 打印请求、响应日志
*
* @author liyulin
* @date 2020-07-17
*/
@Component
@Slf4j
public class GatewayLogFilter implements WebFilter, Ordered {

@Override
public int getOrder() {
return Order.REQUEST_LOG;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
fillLog(exchange.getRequest());

LogServerHttpRequestDecorator requestDecorator = new LogServerHttpRequestDecorator(exchange.getRequest());
LogServerHttpResponseDecorator responseWrapper = new LogServerHttpResponseDecorator(exchange.getResponse());

return chain.filter(exchange.mutate().request(requestDecorator).response(responseWrapper).build()).doFinally(signalType -> {
if (SignalType.ON_ERROR.compareTo(signalType) != 0) {
ApiLogDO apiLogDO = LogUtil.getApiLogCache().get();
if (apiLogDO != null) {
apiLogDO.setCost(System.currentTimeMillis() - apiLogDO.getCost());
log.info("gateway.log=>{}", apiLogDO);
}
}
LogUtil.getApiLogCache().remove();
});
}

private void fillLog(ServerHttpRequest request) {
final String path = request.getURI().getPath();
final String query = request.getURI().getQuery();

ApiLogDO apiLogDO = new ApiLogDO();
// 此处cost存储请求开始时间
apiLogDO.setCost(System.currentTimeMillis());
apiLogDO.setMethod(request.getMethod().name());
apiLogDO.setUrl(path + (StringUtils.isBlank(query) ? "" : "?" + query));
apiLogDO.setHead(request.getHeaders().toString());
LogUtil.getApiLogCache().set(apiLogDO);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.smartframework.cloud.examples.support.gateway.filter.log;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;

/**
* 包装ServerHttpRequest对象,获取请求body参数
* @author liyulin
* @date 2020-07-21
*/
@Slf4j
public class LogServerHttpRequestDecorator extends ServerHttpRequestDecorator {

private Flux<DataBuffer> body;

LogServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
Flux<DataBuffer> flux = super.getBody();
if (LogUtil.legalLogMediaTypes.contains(delegate.getHeaders().getContentType())) {
body = flux.publishOn(Schedulers.single()).map(dataBuffer ->
LogUtil.chain(LogUtil.DataType.REQUEST, dataBuffer, LogUtil.getApiLogCache().get()));
} else {
body = flux;
}
}

@Override
public Flux<DataBuffer> getBody() {
return body;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.smartframework.cloud.examples.support.gateway.filter.log;

import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

/**
* 包装ServerHttpResponse,获取响应结果
*
* @author liyulin
* @date 2020-07-21
*/
@Slf4j
public class LogServerHttpResponseDecorator extends ServerHttpResponseDecorator {

LogServerHttpResponseDecorator(ServerHttpResponse delegate) {
super(delegate);
}

@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return super.writeAndFlushWith(body);
}

@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
final MediaType contentType = super.getHeaders().getContentType();
if (LogUtil.legalLogMediaTypes.contains(contentType)) {
if (body instanceof Mono) {
final Mono<DataBuffer> monoBody = (Mono<DataBuffer>) body;
return super.writeWith(monoBody.publishOn(Schedulers.single())
.map(dataBuffer
-> LogUtil.chain(LogUtil.DataType.RESPONSE, dataBuffer, LogUtil.getApiLogCache().get())));
} else if (body instanceof Flux) {
final Flux<DataBuffer> monoBody = (Flux<DataBuffer>) body;
return super.writeWith(monoBody.publishOn(Schedulers.single())
.map(dataBuffer
-> LogUtil.chain(LogUtil.DataType.RESPONSE, dataBuffer, LogUtil.getApiLogCache().get())));
}
}
return super.writeWith(body);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.smartframework.cloud.examples.support.gateway.filter.log;

import com.google.common.collect.Lists;
import io.netty.buffer.UnpooledByteBufAllocator;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.MediaType;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
* @author liyulin
* @date 2020-07-21
*/
@Slf4j
public class LogUtil {

/**
* 打印日志的http content-type类型
*/
public static final List<MediaType> legalLogMediaTypes = Lists.newArrayList(MediaType.APPLICATION_XML,
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_JSON_UTF8,
MediaType.TEXT_PLAIN,
MediaType.TEXT_XML);

/**
* 存储临时日志
*/
@Getter
private static ThreadLocal<ApiLogDO> apiLogCache = new InheritableThreadLocal<>();

public static <T extends DataBuffer> T chain(DataType dataType, T buffer, ApiLogDO apiLogDO) {
try {
InputStream dataBuffer = buffer.asInputStream();
byte[] bytes = IOUtils.toByteArray(dataBuffer);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));

String data = new String(bytes, StandardCharsets.UTF_8);
// 请求数据
if (dataType == DataType.REQUEST) {
apiLogDO.setArgs(data);
}
// 响应数据
else if (data != null) {
// 超过长度的截掉
apiLogDO.setResult(data.length() <= 1024 ? data : data.substring(0, 1024));
}

DataBufferUtils.release(buffer);
return (T) nettyDataBufferFactory.wrap(bytes);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return null;
}

/**
* 数据类型
*/
static enum DataType {
/**
* 请求数据
*/
REQUEST,
/**
* 响应数据
*/
RESPONSE;
}

}

0 comments on commit 5ada67d

Please sign in to comment.