sentry-java icon indicating copy to clipboard operation
sentry-java copied to clipboard

Sentry-graphql does not send any GraphQL operations as transactions

Open Andrevmatias opened this issue 1 year ago • 6 comments

Integration

sentry-graphql

Java Version

17

Version

7.6.0

Steps to Reproduce

Using Spring Boot 2.7.8 and the spring boot sentry integration.

Instrumentation configuration:

@Configuration
class SentryConfiguration(
    val tracingDataBuilder: TracingDataBuilder
) {
    @Bean
    fun sentryTracingInstrumentation() = SentryInstrumentation(
        { span: ISpan, env: DataFetchingEnvironment, _: Any? ->
            tracingDataBuilder.createTags(env.operationDefinition.operation.toString()).forEach { (key, value) ->
                span.setTag(key, value)
            }
            span
        },
        SentrySpringSubscriptionHandler(),
        false
    )

    @Bean
    fun beforeSendTransactionCallback() = BeforeSendTransactionCallback { sentryTransaction, _ ->
        if (excludedRoutes.contains(sentryTransaction.transaction)) {
            null
        } else {
            sentryTransaction
        }
    }
}

application.properties

sentry.enabled=true
sentry.debug=true
sentry.dsn=[dsn]
[email protected]@
[email protected]@
sentry.traces-sample-rate=1.0

Expected Result

Sentry should send all GraphQL requests as transactions and spans for each query/mutation field.

Actual Result

No GraphQL transaction is being sent. Other transactions are working fine. The Debug info also does not show any GraphQL transaction.

Trying to figure out what was happening I investigated the SentryInstrumentation class and couldn't find where the transaction is supposed to be finished. I only found routines to finish the spans.

Andrevmatias avatar Mar 22 '24 21:03 Andrevmatias

Hello @Andrevmatias I just tested using our Spring Boot 2 sample and there's a transaction for the request which the GraphQL span is attached to. Maybe you can spot a difference to your setup by looking at our sample.

You can also attach a debugger to SentryInstrumentation line number 306 and check if there's an actual transaction there or if it's a NoOpTransaction.

adinauer avatar Mar 25 '24 10:03 adinauer

Thanks @adinauer!

The only difference I noticed is that we use the graphql.kickstart.tools lib and don't specify the sentry.enable-tracing=true property (as it's deprecated).

The transaction value at line 306 is an instance of SentryTracer. It's finish method is never called. Isn't it the method responsible to send the event? So, these events are never sent to the sentry server, while "on demand" events are working fine.

Andrevmatias avatar Mar 25 '24 17:03 Andrevmatias

@Andrevmatias finish should be called by https://github.com/getsentry/sentry-java/blob/695d3a3acd91e7759da0699f9b5e004d945de53b/sentry-spring/src/main/java/io/sentry/spring/tracing/SentryTracingFilter.java#L126 if you're using Spring Boot 2 WebMVC.

Can you add a breakpoint there and see what's happening?

adinauer avatar Mar 26 '24 17:03 adinauer

It never reaches this line while resolving GraphQL requests because transactionName is null.

It happens because the request does not have the BEST_MATCHING_PATTERN_ATTRIBUTE set. It seems that this attribute is mandatory for the SpringMvcTransactionNameProvider to work.

Looking at the code, even if this attribute is set, all the GraphQL requests would be grouped into the POST /api/graphql transaction, right? I thought every query/mutation would be in a separate transaction. Is this possible?

I implemented a custom TransactionNameProvider that returns a fixed name and it worked fine, but I couldn't find a way to give the transaction the name of the GraphQL operation.

So, two questions:

  1. Is there any configuration step I missed to make BEST_MATCHING_PATTERN_ATTRIBUTE to be set? I can't find anything in the docs.
  2. Is it possible to separate every GraphQL operation in its own transaction?

Andrevmatias avatar Mar 29 '24 17:03 Andrevmatias

@Andrevmatias

  1. Is there any configuration step I missed to make BEST_MATCHING_PATTERN_ATTRIBUTE to be set? I can't find anything in the docs.

There's cases where this attribute is not set, it seems your setup is one of them (Maybe because you don't have Spring WebMVC?). Event then yes, it'd only set POST /api/graphql.

I thought every query/mutation would be in a separate transaction. Is this possible?

  1. Is it possible to separate every GraphQL operation in its own transaction?

Currently no.

I think you best option at the moment is to implement your own TransactionNameProvider and return the name of the query from it by extracting it from the HttpServletRequest. You'll likely have to parse the request. Not quite sure what to do with multiple operations in one request - maybe you can just concat them.

Sorry I don't have a better solution at the moment. If something comes to mind, I'll update here.

The following will require some SDK changes. Ideas to make changing the name easier in the future:

  • allow setting transaction name in beforeSendTransaction
  • pass more details into TransactionNameProvider (e.g. pass in the transaction)

Ideas to make promoting spans to transactions easier in the future:

  • when using OTEL for performance add some flag that tells our SpanProcessor/SpanExporter to convert a span to a transaction.

adinauer avatar Apr 02 '24 06:04 adinauer

There's cases where this attribute is not set, it seems your setup is one of them (Maybe because you don't have Spring >WebMVC?). Event then yes, it'd only set POST /api/graphql.

I do have Spring WebMVC. Calls to REST Controllers are sent correctly but I think the attribute is not set when using the graphql.kickstart.tools structure.

For future reference, my solution was to implement my own TransactionNameProvider:

@Bean
fun transactionNameProvider() = TransactionNameProvider {
    val pattern = it.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE) as String?
    "${it.method} ${pattern ?: it.requestURI}"
}

About splitting into multiple transactions:

As the transactions were not being finished, I implemented a custom SentryInstrumentation that starts the transaction in beginExecution and finishes it in instrumentExecutionResult. It seems to work as expected.

Andrevmatias avatar Apr 02 '24 12:04 Andrevmatias