Sentry-graphql does not send any GraphQL operations as transactions
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.
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.
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 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?
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:
- Is there any configuration step I missed to make
BEST_MATCHING_PATTERN_ATTRIBUTEto be set? I can't find anything in the docs. - Is it possible to separate every GraphQL operation in its own transaction?
@Andrevmatias
- 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?
- 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/SpanExporterto convert a span to a transaction.
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.