2 changed files with 218 additions and 0 deletions
@ -0,0 +1,119 @@
|
||||
/* |
||||
* Copyright (c) 2018-2028, DreamLu All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* Neither the name of the dreamlu.net developer nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* Author: DreamLu 卢春梦 (596392912@qq.com) |
||||
*/ |
||||
package org.springblade.gateway.filter; |
||||
|
||||
import io.jsonwebtoken.Claims; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.springblade.core.jwt.JwtUtil; |
||||
import org.springblade.gateway.provider.AuthProvider; |
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
||||
import org.springframework.cloud.gateway.filter.GlobalFilter; |
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.net.URI; |
||||
import java.util.ArrayList; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* webflux 日志请求记录,方便开发调试。请求日志过滤器排序尽量低。 |
||||
* |
||||
* <p> |
||||
* 注意:暂时不支持结构体打印,想实现,请看下面的链接。 |
||||
* https://stackoverflow.com/questions/45240005/how-to-log-request-and-response-bodies-in-spring-webflux
|
||||
* https://github.com/Silvmike/webflux-demo/blob/master/tests/src/test/java/ru/hardcoders/demo/webflux/web_handler/filters/logging
|
||||
* </p> |
||||
* |
||||
* @author dream.lu |
||||
*/ |
||||
@Slf4j |
||||
@Configuration |
||||
@RequiredArgsConstructor |
||||
@ConditionalOnProperty(value = "blade.log.request.enabled", havingValue = "true", matchIfMissing = true) |
||||
public class GlobalRequestLogFilter implements GlobalFilter, Ordered { |
||||
private final WebEndpointProperties endpointProperties; |
||||
|
||||
@Override |
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
||||
ServerHttpRequest request = exchange.getRequest(); |
||||
// 打印请求路径
|
||||
String path = request.getPath().pathWithinApplication().value(); |
||||
|
||||
// 忽略 endpoint 请求
|
||||
String endpointBasePath = endpointProperties.getBasePath(); |
||||
if (StringUtils.isNotBlank(endpointBasePath) && path.startsWith(endpointBasePath)) { |
||||
return chain.filter(exchange); |
||||
} |
||||
|
||||
LinkedHashSet<URI> uris = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR); |
||||
URI requestUri = uris.stream().findFirst().orElse(request.getURI()); |
||||
MultiValueMap<String, String> queryParams = request.getQueryParams(); |
||||
String requestUrl = UriComponentsBuilder.fromPath(requestUri.getRawPath()).queryParams(queryParams).build().toUriString(); |
||||
|
||||
// 构建成一条长 日志,避免并发下日志错乱
|
||||
StringBuilder beforeReqLog = new StringBuilder(300); |
||||
// 日志参数
|
||||
List<Object> beforeReqArgs = new ArrayList<>(); |
||||
beforeReqLog.append("\n\n================ Gateway Request Start ================\n"); |
||||
// 打印路由
|
||||
beforeReqLog.append("===> {}: {}\n"); |
||||
// 参数
|
||||
String requestMethod = request.getMethodValue(); |
||||
beforeReqArgs.add(requestMethod); |
||||
beforeReqArgs.add(requestUrl); |
||||
|
||||
// 打印请求头
|
||||
HttpHeaders headers = request.getHeaders(); |
||||
headers.forEach((headerName, headerValue) -> { |
||||
beforeReqLog.append("===Headers=== {}: {}\n"); |
||||
beforeReqArgs.add(headerName); |
||||
if (AuthProvider.AUTH_KEY.toLowerCase().equals(headerName)) { |
||||
String value = headerValue.get(0); |
||||
String token = JwtUtil.getToken(value); |
||||
Claims claims = JwtUtil.parseJWT(token); |
||||
beforeReqArgs.add(claims.toString()); |
||||
beforeReqLog.append("===Headers=== {}: {}\n"); |
||||
beforeReqArgs.add(headerName.concat("-original")); |
||||
beforeReqArgs.add(StringUtils.join(headerValue)); |
||||
} else { |
||||
beforeReqArgs.add(StringUtils.join(headerValue)); |
||||
} |
||||
}); |
||||
|
||||
beforeReqLog.append("================ Gateway Request End =================\n"); |
||||
// 打印执行时间
|
||||
log.info(beforeReqLog.toString(), beforeReqArgs.toArray()); |
||||
return chain.filter(exchange); |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return Ordered.LOWEST_PRECEDENCE; |
||||
} |
||||
} |
@ -0,0 +1,99 @@
|
||||
/* |
||||
* Copyright (c) 2018-2028, DreamLu All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* Neither the name of the dreamlu.net developer nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* Author: DreamLu 卢春梦 (596392912@qq.com) |
||||
*/ |
||||
package org.springblade.gateway.filter; |
||||
|
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
||||
import org.springframework.cloud.gateway.filter.GlobalFilter; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.core.Ordered; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
import org.springframework.http.server.reactive.ServerHttpResponse; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* webflux 相应日志,方便开发调试,注意排序要优先。 |
||||
* |
||||
* @author dream.lu |
||||
*/ |
||||
@Slf4j |
||||
@Configuration |
||||
@RequiredArgsConstructor |
||||
@ConditionalOnProperty(value = "blade.log.request.enabled", havingValue = "true", matchIfMissing = true) |
||||
public class GlobalResponseLogFilter implements GlobalFilter, Ordered { |
||||
private final WebEndpointProperties endpointProperties; |
||||
|
||||
@Override |
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
||||
ServerHttpRequest request = exchange.getRequest(); |
||||
// 打印请求路径
|
||||
String path = request.getPath().pathWithinApplication().value(); |
||||
// 忽略 endpoint 请求
|
||||
String endpointBasePath = endpointProperties.getBasePath(); |
||||
if (StringUtils.isNotBlank(endpointBasePath) && path.startsWith(endpointBasePath)) { |
||||
return chain.filter(exchange); |
||||
} |
||||
return chain.filter(exchange).then( |
||||
Mono.fromRunnable(() -> { |
||||
MultiValueMap<String, String> queryParams = request.getQueryParams(); |
||||
String requestUrl = UriComponentsBuilder.fromPath(path).queryParams(queryParams).build().toUriString(); |
||||
|
||||
// 构建成一条长 日志,避免并发下日志错乱
|
||||
StringBuilder responseLog = new StringBuilder(300); |
||||
// 日志参数
|
||||
List<Object> responseArgs = new ArrayList<>(); |
||||
responseLog.append("\n\n================ Gateway Response Start ================\n"); |
||||
ServerHttpResponse response = exchange.getResponse(); |
||||
// 打印路由 200 get: /api/xxx/xxx
|
||||
responseLog.append("<=== {} {}: {}\n"); |
||||
// 参数
|
||||
String requestMethod = request.getMethodValue(); |
||||
responseArgs.add(response.getStatusCode().value()); |
||||
responseArgs.add(requestMethod); |
||||
responseArgs.add(requestUrl); |
||||
|
||||
// 打印请求头
|
||||
HttpHeaders headers = response.getHeaders(); |
||||
headers.forEach((headerName, headerValue) -> { |
||||
responseLog.append("===Headers=== {}: {}\n"); |
||||
responseArgs.add(headerName); |
||||
responseArgs.add(StringUtils.join(headerValue)); |
||||
}); |
||||
|
||||
responseLog.append("================ Gateway Response End =================\n"); |
||||
// 打印执行时间
|
||||
log.info(responseLog.toString(), responseArgs.toArray()); |
||||
}) |
||||
); |
||||
} |
||||
|
||||
@Override |
||||
public int getOrder() { |
||||
return Ordered.HIGHEST_PRECEDENCE; |
||||
} |
||||
} |
Loading…
Reference in new issue