Michael
Michael

Reputation: 582

How to add instrumentation to GraphQL Java with graphql-spring-boot?

does anybody know how I can add instrumentation to a GraphQL execution when using graphql-spring-boot (https://github.com/graphql-java-kickstart/graphql-spring-boot) ? I know how this is possible with plain-vanilla graphql-java: https://www.graphql-java.com/documentation/v13/instrumentation/

However, I don't know how to do this when graphql-spring-boot is used and takes control over the execution. Due to lack of documentation I tried it simply this way:

@Service
public class GraphQLInstrumentationProvider implements InstrumentationProvider {
    @Override
    public Instrumentation getInstrumentation() {
        return SimpleInstrumentation.INSTANCE;
    }
}

But the method getInstrumentation on my InstrumentationProvider bean is (as expected) never called. Any help appreciated.

Upvotes: 4

Views: 4367

Answers (2)

Flame239
Flame239

Reputation: 1354

There is a simpler way to add instrumentation with spring boot:

@Configuration
public class InstrumentationConfiguration {
    @Bean
    public Instrumentation someFieldCheckingInstrumentation() {
        return new FieldValidationInstrumentation(env -> {
            // ... 
        });
    }
}

Spring boot will collect all beans which implement Instrumentation (see GraphQLWebAutoConfiguration).

Upvotes: 2

Michael
Michael

Reputation: 582

Answering my own question. In the meantime I managed to do it this way:

final class RequestLoggingInstrumentation extends SimpleInstrumentation {

    private static final Logger logger = LoggerFactory.getLogger(RequestLoggingInstrumentation.class);

    @Override
    public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
        long startMillis = System.currentTimeMillis();
        var executionId = parameters.getExecutionInput().getExecutionId();

        if (logger.isInfoEnabled()) {
            logger.info("GraphQL execution {} started", executionId);

            var query = parameters.getQuery();
            logger.info("[{}] query: {}", executionId, query);
            if (parameters.getVariables() != null && !parameters.getVariables().isEmpty()) {
                logger.info("[{}] variables: {}", executionId, parameters.getVariables());
            }
        }

        return new SimpleInstrumentationContext<>() {
            @Override
            public void onCompleted(ExecutionResult executionResult, Throwable t) {
                if (logger.isInfoEnabled()) {
                    long endMillis = System.currentTimeMillis();

                    if (t != null) {
                        logger.info("GraphQL execution {} failed: {}", executionId, t.getMessage(), t);
                    } else {
                        var resultMap = executionResult.toSpecification();
                        var resultJSON = ObjectMapper.pojoToJSON(resultMap).replace("\n", "\\n");
                        logger.info("[{}] completed in {}ms", executionId, endMillis - startMillis);
                        logger.info("[{}] result: {}", executionId, resultJSON);
                    }
                }
            }
        };
    }
}

@Service
class InstrumentationService {

    private final ContextFactory contextFactory;

    InstrumentationService(ContextFactory contextFactory) {
        this.contextFactory = contextFactory;
    }

    /**
     * Return all instrumentations as a bean.
     * The result will be used in class {@link com.oembedler.moon.graphql.boot.GraphQLWebAutoConfiguration}.
     */
    @Bean
    List<Instrumentation> instrumentations() {
        // Note: Due to a bug in GraphQLWebAutoConfiguration, the returned list has to be modifiable (it will be sorted)
        return new ArrayList<>(
                List.of(new RequestLoggingInstrumentation()));
    }
}

It helped me to have a look into the class GraphQLWebAutoConfiguration. There I found out that the framework expects a bean of type List<Instrumentation>, which contains all the instrumentations that will be added to the GraphQL execution.

Upvotes: 2

Related Questions