Spring Cloud Gateway 记录请求应答数据日志
public class GatewayContext {
public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext" ;
private String cacheBody;
private MultiValueMap < String , String > formData;
private String path;
public String getCacheBody ( ) {
return cacheBody;
}
public void setCacheBody ( String cacheBody) {
this . cacheBody = cacheBody;
}
public MultiValueMap < String , String > getFormData ( ) {
return formData;
}
public void setFormData ( MultiValueMap < String , String > formData) {
this . formData = formData;
}
public String getPath ( ) {
return path;
}
public void setPath ( String path) {
this . path = path;
}
}
import java. io. UnsupportedEncodingException ;
import java. net. URLEncoder ;
import java. nio. charset. Charset ;
import java. nio. charset. StandardCharsets ;
import java. util. List ;
import java. util. Map ;
import org. slf4j. Logger ;
import org. slf4j. LoggerFactory ;
import org. springframework. cloud. gateway. filter. GatewayFilterChain ;
import org. springframework. cloud. gateway. filter. GlobalFilter ;
import org. springframework. core. io. ByteArrayResource ;
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. HttpHeaders ;
import org. springframework. http. HttpMethod ;
import org. springframework. http. MediaType ;
import org. springframework. http. codec. HttpMessageReader ;
import org. springframework. http. server. reactive. ServerHttpRequest ;
import org. springframework. http. server. reactive. ServerHttpRequestDecorator ;
import org. springframework. stereotype. Component ;
import org. springframework. util. MultiValueMap ;
import org. springframework. web. reactive. function. server. HandlerStrategies ;
import org. springframework. web. reactive. function. server. ServerRequest ;
import org. springframework. web. server. ServerWebExchange ;
import io. netty. buffer. ByteBufAllocator ;
import reactor. core. publisher. Flux ;
import reactor. core. publisher. Mono ;
@Component
public class LogRequestGlobalFilter
implements GlobalFilter {
private static final List < HttpMessageReader < ? > > messageReaders =
HandlerStrategies . withDefaults ( ) . messageReaders ( ) ;
private Logger log = LoggerFactory . getLogger ( LogRequestGlobalFilter . class ) ;
@Override
public Mono < Void > filter (
ServerWebExchange exchange,
GatewayFilterChain chain) {
ServerHttpRequest request = exchange. getRequest ( ) ;
String path = request. getPath ( ) . pathWithinApplication ( ) . value ( ) ;
GatewayContext gatewayContext = new GatewayContext ( ) ;
gatewayContext. setPath ( path) ;
exchange. getAttributes ( ) . put ( GatewayContext . CACHE_GATEWAY_CONTEXT,
gatewayContext) ;
HttpHeaders headers = request. getHeaders ( ) ;
MediaType contentType = headers. getContentType ( ) ;
log. info ( "start-------------------------------------------------" ) ;
log. info ( "HttpMethod:{},Url:{}" , request. getMethod ( ) ,
request. getURI ( ) . getRawPath ( ) ) ;
log. info ( "Headers token: {}" , headers. getFirst ( "token" ) ) ;
if ( request. getMethod ( ) == HttpMethod . GET) {
log. info ( "end-------------------------------------------------" ) ;
}
if ( request. getMethod ( ) == HttpMethod . POST) {
Mono < Void > voidMono = null ;
if ( MediaType . APPLICATION_JSON. equals ( contentType)
|| MediaType . APPLICATION_JSON_UTF8. equals ( contentType) ) {
voidMono =
readBody ( exchange, chain, gatewayContext) ;
}
if ( MediaType . APPLICATION_FORM_URLENCODED. equals ( contentType) ) {
voidMono =
readFormData ( exchange, chain, gatewayContext) ;
}
return voidMono;
}
return chain. filter ( exchange) ;
}
private Mono < Void > readFormData (
ServerWebExchange exchange,
GatewayFilterChain chain,
GatewayContext gatewayContext) {
final ServerHttpRequest request = exchange. getRequest ( ) ;
HttpHeaders headers = request. getHeaders ( ) ;
return exchange. getFormData ( )
. doOnNext ( multiValueMap -> {
gatewayContext. setFormData ( multiValueMap) ;
log. info ( "Post x-www-form-urlencoded:{}" ,
multiValueMap) ;
log. info (
"end-------------------------------------------------" ) ;
} )
. then ( Mono . defer ( ( ) -> {
Charset charset = headers. getContentType ( ) . getCharset ( ) ;
charset = charset == null ? StandardCharsets . UTF_8 : charset;
String charsetName = charset. name ( ) ;
MultiValueMap < String , String > formData =
gatewayContext. getFormData ( ) ;
if ( null == formData || formData. isEmpty ( ) ) {
return chain. filter ( exchange) ;
}
StringBuilder formDataBodyBuilder = new StringBuilder ( ) ;
String entryKey;
List < String > entryValue;
try {
for ( Map. Entry < String , List < String > > entry : formData
. entrySet ( ) ) {
entryKey = entry. getKey ( ) ;
entryValue = entry. getValue ( ) ;
if ( entryValue. size ( ) > 1 ) {
for ( String value : entryValue) {
formDataBodyBuilder. append ( entryKey) . append ( "=" )
. append (
URLEncoder . encode ( value, charsetName) )
. append ( "&" ) ;
}
} else {
formDataBodyBuilder
. append ( entryKey) . append ( "=" ) . append ( URLEncoder
. encode ( entryValue. get ( 0 ) , charsetName) )
. append ( "&" ) ;
}
}
} catch ( UnsupportedEncodingException e) {
}
String formDataBodyString = "" ;
if ( formDataBodyBuilder. length ( ) > 0 ) {
formDataBodyString = formDataBodyBuilder. substring ( 0 ,
formDataBodyBuilder. length ( ) - 1 ) ;
}
byte [ ] bodyBytes = formDataBodyString. getBytes ( charset) ;
int contentLength = bodyBytes. length;
ServerHttpRequestDecorator decorator =
new ServerHttpRequestDecorator (
request) {
@Override
public HttpHeaders getHeaders ( ) {
HttpHeaders httpHeaders = new HttpHeaders ( ) ;
httpHeaders. putAll ( super . getHeaders ( ) ) ;
if ( contentLength > 0 ) {
httpHeaders. setContentLength ( contentLength) ;
} else {
httpHeaders. set ( HttpHeaders . TRANSFER_ENCODING,
"chunked" ) ;
}
return httpHeaders;
}
@Override
public Flux < DataBuffer > getBody ( ) {
return DataBufferUtils
. read ( new ByteArrayResource ( bodyBytes) ,
new NettyDataBufferFactory (
ByteBufAllocator . DEFAULT) ,
contentLength) ;
}
} ;
ServerWebExchange mutateExchange =
exchange. mutate ( ) . request ( decorator) . build ( ) ;
return chain. filter ( mutateExchange) ;
} ) ) ;
}
private Mono < Void > readBody (
ServerWebExchange exchange,
GatewayFilterChain chain,
GatewayContext gatewayContext) {
return DataBufferUtils . join ( exchange. getRequest ( ) . getBody ( ) )
. flatMap ( dataBuffer -> {
byte [ ] bytes = new byte [ dataBuffer. readableByteCount ( ) ] ;
dataBuffer. read ( bytes) ;
DataBufferUtils . release ( dataBuffer) ;
Flux < DataBuffer > cachedFlux = Flux . defer ( ( ) -> {
DataBuffer buffer =
exchange. getResponse ( ) . bufferFactory ( ) . wrap ( bytes) ;
DataBufferUtils . retain ( buffer) ;
return Mono . just ( buffer) ;
} ) ;
ServerHttpRequest mutatedRequest =
new ServerHttpRequestDecorator ( exchange. getRequest ( ) ) {
@Override
public Flux < DataBuffer > getBody ( ) {
return cachedFlux;
}
} ;
ServerWebExchange mutatedExchange =
exchange. mutate ( ) . request ( mutatedRequest) . build ( ) ;
return ServerRequest . create ( mutatedExchange, messageReaders)
. bodyToMono ( String . class )
. doOnNext ( objectValue -> {
log. info ( "PostBody:{}" , objectValue) ;
log. info (
"end-------------------------------------------------" ) ;
gatewayContext. setCacheBody ( objectValue) ;
} ) . then ( chain. filter ( mutatedExchange) ) ;
} ) ;
}
}
import lombok. extern. slf4j. Slf4j ;
import org. reactivestreams. Publisher ;
import org. springframework. cloud. gateway. filter. GatewayFilterChain ;
import org. springframework. cloud. gateway. filter. GlobalFilter ;
import org. springframework. core. Ordered ;
import org. springframework. core. io. buffer. DataBuffer ;
import org. springframework. core. io. buffer. DataBufferFactory ;
import org. springframework. http. HttpHeaders ;
import org. springframework. http. HttpMethod ;
import org. springframework. http. server. reactive. ServerHttpRequest ;
import org. springframework. http. server. reactive. ServerHttpResponse ;
import org. springframework. http. server. reactive. ServerHttpResponseDecorator ;
import org. springframework. stereotype. Component ;
import org. springframework. web. server. ServerWebExchange ;
import reactor. core. publisher. Flux ;
import reactor. core. publisher. Mono ;
import java. net. InetSocketAddress ;
import java. net. URI;
import java. nio. CharBuffer ;
import java. nio. charset. Charset ;
import java. nio. charset. StandardCharsets ;
import java. util. concurrent. atomic. AtomicReference ;
@Component
@Slf4j
public class LogResponseGlobalFilter implements GlobalFilter , Ordered {
private static final String REQUEST_PREFIX = "Request Info [ " ;
private static final String REQUEST_TAIL = " ]" ;
private static final String RESPONSE_PREFIX = "Response Info [ " ;
private static final String RESPONSE_TAIL = " ]" ;
private StringBuilder normalMsg = new StringBuilder ( ) ;
@Override
public Mono < Void > filter ( ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange. getRequest ( ) ;
ServerHttpResponse response = exchange. getResponse ( ) ;
DataBufferFactory bufferFactory = response. bufferFactory ( ) ;
normalMsg. append ( RESPONSE_PREFIX) ;
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator ( response) {
@Override
public Mono < Void > writeWith ( Publisher < ? extends DataBuffer > body) {
if ( body instanceof Flux ) {
Flux < ? extends DataBuffer > fluxBody = ( Flux < ? extends DataBuffer > ) body;
return super . writeWith ( fluxBody. map ( dataBuffer -> {
byte [ ] content = new byte [ dataBuffer. readableByteCount ( ) ] ;
dataBuffer. read ( content) ;
DataBufferUtils . release ( dataBuf) ;
String responseResult = new String ( content, Charset . forName ( "UTF-8" ) ) ;
normalMsg. append ( "status=" ) . append ( this . getStatusCode ( ) ) ;
normalMsg. append ( ";header=" ) . append ( this . getHeaders ( ) ) ;
normalMsg. append ( ";responseResult=" ) . append ( responseResult) ;
normalMsg. append ( RESPONSE_TAIL) ;
log. info ( normalMsg. toString ( ) ) ;
return bufferFactory. wrap ( content) ;
} ) ) ;
}
return super . writeWith ( body) ;
}
} ;
return chain. filter ( exchange. mutate ( ) . response ( decoratedResponse) . build ( ) ) ;
}
@Override
public int getOrder ( ) {
return - 2 ;
}
}