question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Request content preview isn't produced if decorate a service with `ThrottlingService`

See original GitHub issue

I found that RequestLog#requestContentPreview returns null when I decorate a service using ContentPreviewingService and ThrottlingService.

The request content preview is not produced when aggregate a service which is decorated with following structure:

.decorator(((delegate, ctx, req) -> HttpResponse.from(
     completedFuture(null).handleAsync((ignored, cause) -> {
         try {
             return delegate.serve(ctx, req);
         } catch (Exception e) {
             return Exceptions.throwUnsafely(e);
         }
     }, ctx.eventLoop()))))

Following test can reproduce:

class RequestContentPreviewTest {

    private static final AtomicReference<ServiceRequestContext> ctxCaptor = new AtomicReference<>();

    @RegisterExtension
    static ServerExtension server = new ServerExtension() {
        @Override
        protected void configure(ServerBuilder builder) {
            builder.service("/", (ctx, req) -> {
                       ctxCaptor.set(ctx);
                       return HttpResponse.from(req.aggregate().thenApply(
                               aggregated -> HttpResponse.of("Hello, " + aggregated.contentUtf8() + "!")));
                   })
                   .decorator(LoggingService.builder()
                                            .requestLogLevel(LogLevel.INFO)
                                            .successfulResponseLogLevel(LogLevel.INFO)
                                            .failureResponseLogLevel(LogLevel.WARN)
                                            .newDecorator())
                   .decorator(ContentPreviewingService.newDecorator(Integer.MAX_VALUE))
                   .decorator(((delegate, ctx, req) -> HttpResponse.from(
                           completedFuture(null).handleAsync((ignored, cause) -> {
                               try {
                                   return delegate.serve(ctx, req);
                               } catch (Exception e) {
                                   return Exceptions.throwUnsafely(e);
                               }
                           }, ctx.eventLoop()))));
        }
    };

    @Test
    void requestContentPreviewH1C() {
        final WebClient client = WebClient.of(SessionProtocol.H1C,
                                              Endpoint.of("127.0.0.1", server.httpPort()));
        final RequestHeaders headers = RequestHeaders.of(HttpMethod.POST, "/",
                                                         HttpHeaderNames.CONTENT_TYPE, "text/plain");
        final AggregatedHttpResponse res = client.execute(headers, HttpData.ofUtf8("Armeria"))
                                                 .aggregate()
                                                 .join();
        assertThat(res.contentUtf8()).isEqualTo("Hello, Armeria!");

        final ServiceRequestContext ctx = ctxCaptor.get();
        final RequestLog log = ctx.log().whenComplete().join();
        assertThat(log.responseContentPreview()).isEqualTo("Hello, Armeria!");
        // expected: "Armeria"
        // but was: null
        // org.opentest4j.AssertionFailedError:
        // expected: "Hello, World!"
        assertThat(log.requestContentPreview()).isEqualTo("Armeria");
    }

    @Test
    void requestContentPreviewHTTP() {
        final WebClient client = WebClient.of(SessionProtocol.HTTP,
                                              Endpoint.of("127.0.0.1", server.httpPort()));
        final RequestHeaders headers = RequestHeaders.of(HttpMethod.POST, "/",
                                                         HttpHeaderNames.CONTENT_TYPE, "text/plain");
        final AggregatedHttpResponse res = client.execute(headers, HttpData.ofUtf8("Armeria"))
                                                 .aggregate()
                                                 .join();
        assertThat(res.contentUtf8()).isEqualTo("Hello, Armeria!");

        final ServiceRequestContext ctx = ctxCaptor.get();
        final RequestLog log = ctx.log().whenComplete().join();
        assertThat(log.responseContentPreview()).isEqualTo("Hello, Armeria!");
        assertThat(log.requestContentPreview()).isEqualTo("Armeria");
    }
}

logging H1C:

02:08:33.724 [armeria-common-worker-nio-2-2] INFO  c.l.a.server.logging.LoggingService - [sreqId=64b5968f, chanId=ec5eedd2, raddr=127.0.0.1:54446, laddr=127.0.0.1:54445][h1c://al01540922.local/#POST] Request: {startTime=2021-09-08T17:08:33.696Z(1631120913696000), length=7B, duration=24472µs(24472956ns), scheme=none+h1c, name=POST, headers=[:method=POST, :path=/, :scheme=http, host=127.0.0.1:54445, content-type=text/plain, user-agent=armeria/1.11.1-SNAPSHOT, content-length=7]}
02:08:33.741 [armeria-common-worker-nio-2-2] INFO  c.l.a.server.logging.LoggingService - [sreqId=64b5968f, chanId=ec5eedd2, raddr=127.0.0.1:54446, laddr=127.0.0.1:54445][h1c://al01540922.local/#POST] Response: {startTime=2021-09-08T17:08:33.737Z(1631120913737000), length=15B, duration=3305µs(3305059ns), totalDuration=43624µs(43624769ns), headers=[:status=200, content-type=text/plain; charset=utf-8, content-length=15], contentPreview=Hello, Armeria!}

logging H2C:

02:08:33.884 [armeria-common-worker-nio-2-4] INFO  c.l.a.server.logging.LoggingService - [sreqId=d4e30197, chanId=1d1d7144, raddr=127.0.0.1:54447, laddr=127.0.0.1:54445][h2c://al01540922.local/#POST] Request: {startTime=2021-09-08T17:08:33.882Z(1631120913882000), length=7B, duration=1976µs(1976048ns), scheme=none+h2c, name=POST, headers=[:method=POST, :path=/, :scheme=http, :authority=127.0.0.1:54445, content-type=text/plain, user-agent=armeria/1.11.1-SNAPSHOT, content-length=7], contentPreview=Armeria}
02:08:33.886 [armeria-common-worker-nio-2-4] INFO  c.l.a.server.logging.LoggingService - [sreqId=d4e30197, chanId=1d1d7144, raddr=127.0.0.1:54447, laddr=127.0.0.1:54445][h2c://al01540922.local/#POST] Response: {startTime=2021-09-08T17:08:33.884Z(1631120913884000), length=15B, duration=1648µs(1648011ns), totalDuration=3309µs(3309007ns), headers=[:status=200, content-type=text/plain; charset=utf-8, content-length=15], contentPreview=Hello, Armeria!}

It works well if not decorated by the last one.

What I found is that https://github.com/line/armeria/blob/dbc7d4d3a324aac525acbdce134fe56c34186a4f/core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java#L62 is called before https://github.com/line/armeria/blob/dbc7d4d3a324aac525acbdce134fe56c34186a4f/core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java#L82

And this is because a CloseEvent from Http1RequestDecoder is already in queue before collect.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:2
  • Comments:7 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
ghkim3221commented, Sep 10, 2021

I think I was confused. Please ignore 😅

1reaction
ikhooncommented, Sep 9, 2021

I found that when a HttpResponse is deferred, handleCloseEvent can be called before delegated service is served in HTTP/1. Maybe is it ok?

When I locally tested it, handleCloseEvent seems to be called by req.aggregate() in service. It should be a bug if handleCloseEvent is called before subscription.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Avoid getting throttled or blocked in SharePoint Online
What happens when you get throttled in SharePoint Online? ... HTTP 503 indicates the service isn't ready to handle the request.
Read more >
Rate-limiting strategies and techniques - Google Cloud
This technique recognizes that not all inputs to a service correspond 1:1 with requests. A token bucket adds tokens at some rate. When...
Read more >
Why is my Guide not displaying? - Pendo Help Center
Use this article to check why your Guide might not be displaying as expected in your staging or production environment. This article...
Read more >
Resolve issues with throttled DynamoDB tables - Amazon AWS
Your DynamoDB table has adequate provisioned capacity, but most of the requests are being throttled. You activated AWS Application Auto ...
Read more >
Android 13 changelog: A deep dive by Mishaal Rahman
There were 2 developer previews and 4 betas during development of Android 13. ... Since no service with this name exists on any...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found