日志收集效果
网关负责服务的转发,所有通过网关转发的服务。都可以通过网关进行收集相关请求日志,具体实现如下:
日志实体类
package ;
import lombok.*;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GatewayLog {
/**请求来源**/
private String origin;
/**访问实例*/
private String targetServer;
/**请求路径*/
private String requestPath;
/**请求方法*/
private String requestMethod;
/**协议 */
private String schema;
/**请求类型 */
private String requestContentType;
/**请求头 */
private String headers;
/**请求体*/
private String requestBody;
/**响应体*/
private String responseData;
/**请求ip*/
private String ip;
/**IP所属城市*/
private String city;
/**开始时间*/
private Long startTime;
/**结束时间*/
private Long endTime;
/**请求时间*/
private String requestTime;
/**响应时间*/
private String responseTime;
/**执行时间*/
private long executeTime;
/**路由配置*/
private String routeConfig;
/**响应状态*/
private String status;
}
网关日志拦截器
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .*;
import ;
import .slf4j.Slf4j;
import .;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Slf4j
@Component
public class GatewayLogFilter implements GlobalFilter, Ordered {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
private final List<HttpMessageReader<?>> messageReaders = ().messageReaders();
private static final String CONTENT_TYPE = "application/json";
// 请求来源应用
private static final String REQUEST_ORIGIN_APP = "Request-Origin-App";
// 自定义请求头,转发之前删除自定义请求头
private static final List<String> CUSTOM_HEADERS = ("sign", "timestamp", "random", "Request-Origin-App");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = ();
// 请求路径
String requestPath = ().pathWithinApplication().value();
// 获取路由信息
Route route = getGatewayRoute(exchange);
String ipAddress = (request);
GatewayLog gatewayLog = new GatewayLog();
(().getFirst(REQUEST_ORIGIN_APP));
(().getScheme());
(());
(requestPath);
(().toString());
(new Date().getTime());
(ipAddress);
((route));
Map<String, Object> headers = new HashMap<>();
for (String key : ().keySet()) {
(key, ().getFirst(key));
}
((headers));
MediaType mediaType = ().getContentType();
if (().getContentType() != null) {
(().getContentType().toString());
}
if(mediaType != null && (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType))){
return writeBodyLog(exchange, chain, gatewayLog);
}else{
return writeBasicLog(exchange, chain, gatewayLog);
}
}
@Override
public int getOrder() {
// 必须小于等于-2,否则无法获取相应结果
return -2;
}
/**
* 获取路由信息
* @param exchange
* @return
*/
private Route getGatewayRoute(ServerWebExchange exchange) {
return (ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
}
private Mono<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) {
StringBuilder builder = new StringBuilder();
MultiValueMap<String, String> queryParams = ().getQueryParams();
(getUrlParamsByMap(queryParams));
// 修改Header
ServerHttpRequest mutableReq = ().mutate().headers(httpHeaders -> {
// 删除自定义header
for (String customHeader : CUSTOM_HEADERS) {
(customHeader);
}
}).build();
//获取响应体
ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog);
return (().request(mutableReq).response(decoratedResponse).build())
.then((() -> {
// 打印日志
writeAccessLog(gatewayLog);
}));
}
/**
* 解决 request body 只能读取一次问题,
* 参考:
* @param exchange
* @param chain
* @param gatewayLog
* @return
*/
private Mono<Void> writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) {
ServerRequest serverRequest = (exchange,messageReaders);
Mono<String> modifiedBody = ()
.flatMap(body ->{
(body);
return (body);
});
// 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次
BodyInserter bodyInserter = (modifiedBody, );
HttpHeaders headers = new HttpHeaders();
(().getHeaders());
// the new content type will be computed by bodyInserter
// and then set in the request decorator
(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return (outputMessage,new BodyInserterContext())
.then((() -> {
// 重新封装请求
ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage);
// 记录响应日志
ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog);
// 记录普通的
return (().request(decoratedRequest).response(decoratedResponse).build())
.then((() -> {
// 打印日志
writeAccessLog(gatewayLog);
}));
}));
}
/**
* 打印日志
* @param gatewayLog 网关日志
*/
private void writeAccessLog(GatewayLog gatewayLog) {
(new GatewayLogEvent(this, gatewayLog));
}
/**
* 请求装饰器,重新计算 headers
* @param exchange
* @param headers
* @param outputMessage
* @return
*/
private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers,
CachedBodyOutputMessage outputMessage) {
return new ServerHttpRequestDecorator(()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = ();
HttpHeaders httpHeaders = new HttpHeaders();
(());
if (contentLength > 0) {
(contentLength);
} else {
// TODO: this causes a 'HTTP/1.1 411 Length Required' // on
//
(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
// 删除自定义header
for (String customHeader : CUSTOM_HEADERS) {
(customHeader);
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return ();
}
};
}
/**
* 记录响应日志
* 通过 DataBufferFactory 解决响应体分段传输问题。
*/
private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) {
ServerHttpResponse response = ();
DataBufferFactory bufferFactory = ();
return new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Date responseTime = new Date();
(());
// 计算执行时间
long executeTime = (() - ());
(executeTime);
(().value() == 200 ? "成功" : "失败");
// 获取响应类型,如果是 json 就打印
String originalResponseContentType = (ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
if (((), )
&& (originalResponseContentType)
&& (CONTENT_TYPE)) {
Flux<? extends DataBuffer> fluxBody = (body);
return (().map(dataBuffers -> {
// 合并多个流集合,解决返回体分段传输
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = (dataBuffers);
byte[] content = new byte[()];
(content);
// 释放掉内存
(join);
String responseResult = new String(content, StandardCharsets.UTF_8);
(responseResult);
return (content);
}));
}
}
// if body is not a flux. never got there.
return (body);
}
};
}
/**
* 将map参数转换成url参数
* @param map
* @return
*/
private String getUrlParamsByMap(MultiValueMap<String, String> map) {
if ((map)) {
return "";
}
StringBuilder sb = new StringBuilder();
for (<String, List<String>> entry : ()) {
(()).append("=").append(().get(0));
("&");
}
String s = ();
if (("&")) {
s = (s, "&");
}
return s;
}
}