Java 如何记录 Spring 5 WebClient 调用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46154994/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 02:16:25  来源:igfitidea点击:

how to log Spring 5 WebClient call

javaspring-bootloggingspring-webflux

提问by Seb

I'm trying to log a request using Spring 5 WebClient. Do you have any idea how could I achieve that?

我正在尝试使用 Spring 5 WebClient 记录请求。你知道我怎么能做到这一点吗?

(I'm using Spring 5 and Spring boot 2)

(我使用的是 Spring 5 和 Spring Boot 2)

The code looks like this at the moment:

目前的代码如下所示:

try {
    return webClient.get().uri(url, urlParams).exchange().flatMap(response -> response.bodyToMono(Test.class))
            .map(test -> xxx.set(test));
} catch (RestClientException e) {
    log.error("Cannot get counter from opus", e);
    throw e;
}

采纳答案by Ruslan Stelmachenko

You can easily do it using ExchangeFilterFunction

您可以使用ExchangeFilterFunction轻松完成

Just add the custom logRequestfilter when you create your WebClientusing WebClient.Builder.

只需logRequest在创建WebClientusing时添加自定义过滤器WebClient.Builder

Here is the example of such filter and how to add it to the WebClient.

这是此类过滤器的示例以及如何将其添加到WebClient.

@Slf4j
@Component
public class MyClient {

    private final WebClient webClient;

    // Create WebClient instance using builder.
    // If you use spring-boot 2.0, the builder will be autoconfigured for you
    // with the "prototype" scope, meaning each injection point will receive
    // a newly cloned instance of the builder.
    public MyClient(WebClient.Builder webClientBuilder) {
        webClient = webClientBuilder // you can also just use WebClient.builder()
                .baseUrl("https://httpbin.org")
                .filter(logRequest()) // here is the magic
                .build();
    }

    // Just example of sending request
    public void send(String path) {
        ClientResponse clientResponse = webClient
                .get().uri(uriBuilder -> uriBuilder.path(path)
                        .queryParam("param", "value")
                        .build())
                .exchange()
                .block();
        log.info("Response: {}", clientResponse.toEntity(String.class).block());
    }

    // This method returns filter function which will log request data
    private static ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
            clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
            return Mono.just(clientRequest);
        });
    }

}

Then just call myClient.send("get");and log messages should be there.

然后只需调用myClient.send("get");和记录消息就应该在那里。

Output example:

输出示例:

Request: GET https://httpbin.org/get?param=value
header1=value1
header2=value2

回答by Abhijit Sarkar

You don't necessarily need to roll your own logger, reactor.ipc.netty.channel.ChannelOperationsHandlerdoes it for you. Just configure your logging system for that class to log at DEBUG level:

你不一定需要滚动自己的记录器,reactor.ipc.netty.channel.ChannelOperationsHandler为你做。只需为该类配置日志系统以在调试级别进行日志记录:

2017-11-23 12:52:04.562 DEBUG 41449 --- [ctor-http-nio-5] r.i.n.channel.ChannelOperationsHandler   : [id: 0x9183d6da, L:/127.0.0.1:57681 - R:localhost/127.0.0.1:8000] Writing object DefaultFullHttpRequest(decodeResult: success, version: HTTP/1.1, content: UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 0))
GET /api/v1/watch/namespaces/default/events HTTP/1.1
user-agent: ReactorNetty/0.7.1.RELEASE
host: localhost:8000
accept-encoding: gzip
Accept: application/json
content-length: 0

One way to have fewer bugs is to not write code whenever possible.

减少错误的一种方法是尽可能不编写代码。

Nov 2018:

2018 年 11 月

With spring-webflux:5.1.2.RELEASE, the above no longer works. Use the following instead:

使用spring-webflux:5.1.2.RELEASE,以上不再有效。请改用以下内容:

logging.level.org.springframework.web.reactive.function.client.ExchangeFunctions=DEBUG
...
2018-11-06 20:58:58.181 DEBUG 20300 --- [           main] o.s.w.r.f.client.ExchangeFunctions       : [2026fbff] HTTP GET http://localhost:8080/stocks/search?symbol=AAPL
2018-11-06 20:58:58.451 DEBUG 20300 --- [ctor-http-nio-4] o.s.w.r.f.client.ExchangeFunctions       : [2026fbff] Response 400 BAD_REQUEST

To log headers or form body, set the above to TRACElevel; however, that's not enough:

要记录标题或表单正文,请将上述设置为TRACE级别;然而,这还不够:

ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults();
exchangeStrategies
    .messageWriters().stream()
    .filter(LoggingCodecSupport.class::isInstance)
    .forEach(writer -> ((LoggingCodecSupport)writer).setEnableLoggingRequestDetails(true));

client = WebClient.builder()
    .exchangeStrategies(exchangeStrategies)

Mar 2019:

2019 年 3 月

In response to a question in the comment that asked how to log request and response body, I don't know if Spring has such a logger but WebClientis built on Netty, so enabling debug logging for package reactor.ipc.nettyshould work, along with thisanswer.

在回答评论中询问如何记录请求和响应正文的问题时,我不知道 Spring 是否有这样的记录器,但它WebClient是基于 Netty 构建的,因此启用包的调试日志记录reactor.ipc.netty应该可以工作,以及这个答案。

回答by Fletch

If you don't want to log the body, then this is really easy.

如果你不想记录身体,那么这真的很容易。

Spring Boot >= 2.1.0

Spring Boot >= 2.1.0

Add the following to application.properties:

将以下内容添加到 application.properties:

logging.level.org.springframework.web.reactive.function.client.ExchangeFunctions=TRACE
spring.http.log-request-details=true

The second line causes headers to be included in the log.

第二行导致日志中包含标题。

Spring Boot < 2.1.0

弹簧靴 < 2.1.0

Add the following to application.properties:

将以下内容添加到 application.properties:

logging.level.org.springframework.web.reactive.function.client.ExchangeFunctions=TRACE

Instead of the second line above, you need to declare a class like this:

您需要声明一个类,而不是上面的第二行:

@Configuration
static class LoggingCodecConfig {

    @Bean
    @Order(0)
    public CodecCustomizer loggingCodecCustomizer() {
        return (configurer) -> configurer.defaultCodecs()
                .enableLoggingRequestDetails(true);
    }

}

Courtesy of this Brian Clozel answer

感谢这个 Brian Clozel 的回答

回答by Matthew Buckett

You can have netty do logging of the request/responses with by asking it todo wiretaping, if you create your Spring WebClient like this then it enables the wiretap option.

You can have netty do logging of the request/responses with by asking it todo wiretaping, if you create your Spring WebClient like this then it enables the wiretap option.

        WebClient webClient = WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(
                HttpClient.create().wiretap(true)
            ))
            .build()

and then have your logging setup:

and then have your logging setup:

logging.level.reactor.netty.http.client.HttpClient: DEBUG

this will log everything for the request/response (including bodies), but the format is not specific to HTTP so not very readable.

this will log everything for the request/response (including bodies), but the format is not specific to HTTP so not very readable.

回答by StasKolodyuk

@Matthew Buckett answer shows you how to get Netty wire logging. However, the format is not very fancy (it includes hex dump). But it can be easily customized via extending io.netty.handler.logging.LoggingHandler

@Matthew Buckett answer shows you how to get Netty wire logging. However, the format is not very fancy (it includes hex dump). But it can be easily customized via extending io.netty.handler.logging.LoggingHandler

public class HttpLoggingHandler extends LoggingHandler {

    @Override
    protected String format(ChannelHandlerContext ctx, String event, Object arg) {
        if (arg instanceof ByteBuf) {
            ByteBuf msg = (ByteBuf) arg;
            return msg.toString(StandardCharsets.UTF_8);
        }
        return super.format(ctx, event, arg);
    }
}

Then include it in your WebClientconfiguration:

Then include it in your WebClientconfiguration:

HttpClient httpClient = HttpClient.create()
    .tcpConfiguration(tcpClient ->
        tcpClient.bootstrap(bootstrap ->
            BootstrapHandlers.updateLogSupport(bootstrap, new HttpLoggingHandler())));

WebClient
    .builder()
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build()

Example:

Example:

webClient.post()
    .uri("https://postman-echo.com/post")
    .syncBody("{\"foo\" : \"bar\"}")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .block();
2019-09-22 18:09:21.477 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb] REGISTERED
2019-09-22 18:09:21.489 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb] CONNECT: postman-echo.com/35.170.134.160:443
2019-09-22 18:09:21.701 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb, L:/192.168.100.35:55356 - R:postman-echo.com/35.170.134.160:443] ACTIVE
2019-09-22 18:09:21.836 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb, L:/192.168.100.35:55356 - R:postman-echo.com/35.170.134.160:443] READ COMPLETE
2019-09-22 18:09:21.905 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb, L:/192.168.100.35:55356 - R:postman-echo.com/35.170.134.160:443] READ COMPLETE
2019-09-22 18:09:22.036 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb, L:/192.168.100.35:55356 - R:postman-echo.com/35.170.134.160:443] USER_EVENT: SslHandshakeCompletionEvent(SUCCESS)
2019-09-22 18:09:22.082 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : POST /post HTTP/1.1
user-agent: ReactorNetty/0.8.11.RELEASE
host: postman-echo.com
Accept: application/json
Content-Type: text/plain;charset=UTF-8
content-length: 15

{"foo" : "bar"}
2019-09-22 18:09:22.083 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb, L:/192.168.100.35:55356 - R:postman-echo.com/35.170.134.160:443] FLUSH
2019-09-22 18:09:22.086 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb, L:/192.168.100.35:55356 - R:postman-echo.com/35.170.134.160:443] READ COMPLETE
2019-09-22 18:09:22.217 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Sep 2019 15:09:22 GMT
ETag: W/"151-Llbe8OYGC3GeZCxttuAH3BOYBKA"
Server: nginx
set-cookie: sails.sid=s%3APe39li6V8TL8FOJOzSINZRkQlZ7HFAYi.UkLZjfajJqkq9fUfF2Y8N4JOInHNW5t1XACu3fhQYSc; Path=/; HttpOnly
Vary: Accept-Encoding
Content-Length: 337
Connection: keep-alive

{"args":{},"data":"{\"foo\" : \"bar\"}","files":{},"form":{},"headers":{"x-forwarded-proto":"https","host":"postman-echo.com","content-length":"15","accept":"application/json","content-type":"text/plain;charset=UTF-8","user-agent":"ReactorNetty/0.8.11.RELEASE","x-forwarded-port":"443"},"json":null,"url":"https://postman-echo.com/post"}
2019-09-22 18:09:22.243 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb, L:/192.168.100.35:55356 - R:postman-echo.com/35.170.134.160:443] READ COMPLETE

If you want to suppress useless (for you) log entries like (note ACTIVEat the end):

If you want to suppress useless (for you) log entries like (note ACTIVEat the end):

2019-09-22 18:09:21.701 DEBUG   --- [ctor-http-nio-4] c.e.w.c.e.logging.HttpLoggingHandler     : [id: 0x505be2bb, L:/192.168.100.35:55356 - R:postman-echo.com/35.170.134.160:443] ACTIVE

You can override channelActiveand others like so:

You can override channelActiveand others like so:

@Override
public void channelActive(ChannelHandlerContext ctx) {
    ctx.fireChannelActive();
}

The answer is based on https://www.baeldung.com/spring-log-webclient-calls

The answer is based on https://www.baeldung.com/spring-log-webclient-calls

回答by Sergey Ushakov

An update of Feb 2020 for Spring Boot 2.2.4 and Spring 5.2.3:

An update of Feb 2020 for Spring Boot 2.2.4 and Spring 5.2.3:

I did not manage to get spring.http.log-request-details=truedoing its job, and current Spring WebFlux reference suggeststhat some coding needs be done to have headers logged, though the code example uses deprecated exchangeStrategies()method.

I did not manage to get spring.http.log-request-details=truedoing its job, and current Spring WebFlux reference suggeststhat some coding needs be done to have headers logged, though the code example uses deprecated exchangeStrategies()method.

There is still a replacement for the deprecated method, so a compact piece of code for getting headers logged at WebClient level may look like this:

There is still a replacement for the deprecated method, so a compact piece of code for getting headers logged at WebClient level may look like this:

WebClient webClient = WebClient.builder()
    .codecs(configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true))
    .build();

with further

with further

logging.level.org.springframework.web.reactive.function.client.ExchangeFunctions=TRACE

It should be noted though that not all of the headers are available (do exist) at WebFlux ExchangeFunctionslevel, so some more logging at Netty HttpClientlevel may be essential too, as per @Matthew's suggestion:

It should be noted though that not all of the headers are available (do exist) at WebFlux ExchangeFunctionslevel, so some more logging at Netty HttpClientlevel may be essential too, as per @Matthew's suggestion:

WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(
        HttpClient.create()
            .wiretap(true)))
    .build()

with further

with further

logging.level.reactor.netty.http.client.HttpClient: DEBUG

This will get bodies logged too.

This will get bodies logged too.