springspring-bootmicrometerspring-micrometerspring-graphql

Advice on extending DefaultServerRequestObservationConvention with request body data?


Currently, with Spring Boot, all http requests are instrumented through Micrometer (https://docs.spring.io/spring-framework/reference/integration/observability.html). However, for GraphQL requests, the contextual name is the following: http post /graphql. I would like to enhance this and add more information to it, such as the query and operation name, for example, http post /graphql query#testQuery.

However, I'm unable to get the request body data as it seems to be empty, yet context.getCarrier().getContentLength() is returning a non -1. Some advices how to proceed would be much appreciated.

CURL example

curl --location 'http://localhost:8080/graphql' \
--header 'Content-Type: application/json' \
--data '{"query":"{\n    testQuery(id: 23) {\n        result \n    }\n}","variables":{}}'

Java code

package com.company.test;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;

@Configuration
public class CustomServerRequestObservationConvention
    extends DefaultServerRequestObservationConvention {

  @Override
  public String getContextualName(ServerRequestObservationContext context) {
    // Modify only /graphql ones
    try {
      StringBuilder requestBody = new StringBuilder();
      InputStream inputStream = context.getCarrier().getInputStream();
      BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
      String line;
      while ((line = reader.readLine()) != null) {
        requestBody.append(line);
      }
      // perform some string filtering here on requestBody
      String operationName = "query#testQuery";
      return "http post /graphql" + operationName;
    } catch (Exception e) {
      return "http post /graphql";
    }
  }
}



Solution

  • The HTTP observation is not the right place for that, as the HTTP instrumentation should not be aware of the type of request (GraphQL or anything else). As mentioned by Marten in the comments, to achieve this you would need to:

    1. buffer the entire request body
    2. read and parse it using graphql-java

    This has significant performance downsides and will make HTTP observations inconsistent or brittle. There is already a "graphql.request" observation for this.

    Note that a single HTTP request can send multiple queries/mutations so the HTTP request contextual name would be hard to derive here.