Apollo3 Integration

Learn how to capture the performance of Apollo GraphQL client.

To be able to capture transactions, you'll need to first set up performance monitoring.

Sentry's Apollo3 integration provides both the SentryApollo3Interceptor and the SentryApollo3HttpInterceptor, which create a span for each outgoing HTTP request executed with an Apollo Kotlin GraphQL client. The integration also provides extension functions on the ApolloClient.Builder.

To install Apollo3:

Copied
<dependency>
    <groupId>io.sentry</groupId>
    <artifactId>sentry-apollo-3</artifactId>
    <version>7.9.0</version>
</dependency>

For other dependency managers, see the central Maven repository.

Add Interceptors to ApolloClient.Builder using SentryApolloBuilderExtensions:

Copied
import com.apollographql.apollo3.ApolloClient;
import io.sentry.apollo3.SentryApolloBuilderExtensionsKt;

ApolloClient apollo = SentryApolloBuilderExtensionsKt.sentryTracing(new ApolloClient.Builder())
    .serverUrl("https://your-api-host/")
    .build();

Because HttpInterceptors need to be added to the NetworkTransport, the SentryInterceptors have to be added manually if you're using a custom NetworkTransport:

Copied
import com.apollographql.apollo3.ApolloClient;
import com.apollographql.apollo3.network.http.HttpNetworkTransport;
import io.sentry.apollo3.SentryApollo3HttpInterceptor;
import io.sentry.apollo3.SentryApollo3Interceptor;

ApolloClient apollo = new ApolloClient.Builder()
        .networkTransport(
            new HttpNetworkTransport.Builder()
                .serverUrl("https://your-api-host/")
                .addInterceptor(new SentryApollo3HttpInterceptor())
                .build())
        .addInterceptor(new SentryApollo3Interceptor())
        .build();

Configure Global Hub Mode:

Copied
import io.sentry.Sentry;

Sentry.init(options -> {
  ..
}, true)

In Global Hub Mode, all threads use the same Hub.

Make sure that your coroutine has access to the correct Sentry context when launching, by providing an instance of SentryContext:

Copied
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.exception.ApolloException
import io.sentry.kotlin.SentryContext
import kotlinx.coroutines.launch

launch(SentryContext()) {
  val response = try {
    apollo.query(..).toDeferred().await()
  } catch (e: ApolloException) {
    // handle protocol errors
    return@launch
  }
}

Spans created around requests can be modified or dropped using SentryApollo3HttpInterceptor.BeforeSpanCallback passed to SentryApollo3HttpInterceptor or the sentryTracing extension function:

Copied
import com.apollographql.apollo3.ApolloClient;
import io.sentry.apollo3.SentryApolloBuilderExtensionsKt;

ApolloClient apollo = SentryApolloBuilderExtensionsKt.sentryTracing(
            new ApolloClient.Builder(),
            (span, request, response) -> {
              if ("LaunchDetails".equals(span.getOperation())) {
                span.setTag("tag-name", "tag-value");
              }
              return span;
            })
        .serverUrl("https://your-api-host/")
        .build();

This feature automatically captures GraphQL client errors (like bad operations and response codes) as error events and reports them to Sentry. The error event will contain the request and response data, including the url, status_code, and data (stringified query).

Sentry will group GraphQL client errors by the operationName, operationType, and statusCode, so that you can easily see the number of errors that happened for each.

This feature is enabled by default and can be disabled by setting the captureFailedRequests option to false:

Copied
import com.apollographql.apollo3.ApolloClient
import io.sentry.apollo3.sentryTracing

val apollo = ApolloClient.builder()
    .serverUrl("https://your-api-host/")
    .sentryTracing(captureFailedRequests = false)
    .build()

By default, only GraphQL client errors with a response that includes the errors array will be captured as error events.

GraphQL client errors from every target (.* regular expression) are automatically captured, but you can change this behavior by setting the failedRequestTargets option with either a regular expression or a plain String. A plain string must contain at least one of the items from the list. Plain strings don't have to be full matches, meaning the URL of a request is matched when it contains a string provided through the option.

Copied
import com.apollographql.apollo3.ApolloClient
import io.sentry.apollo3.sentryTracing

val apollo = ApolloClient.builder()
    .serverUrl("https://your-api-host/")
    .sentryTracing(captureFailedRequests = true, failedRequestTargets = listOf("myapi.com"))
    .build()

By default, error events won't contain Headers or Cookies, but you can change this behavior by setting the sendDefaultPii option to true:

Copied
Sentry.init { options ->
  options.isSendDefaultPii = true
}

Error events will contain the raw bodies of GraphQL requests and responses, which may include sensitive data. To avoid this, parameterize your queries using the variables field. Relay will then run PII Data Scrubbing, automatically transforming values into [Filtered].

Alternatively, you can customize the event and scrub the data yourself.

The captured error event can be customized or dropped with a BeforeSendCallback:

Copied
import io.sentry.Sentry
import io.sentry.SentryOptions.BeforeSendCallback
import com.apollographql.apollo3.api.http.HttpRequest
import com.apollographql.apollo3.api.http.HttpResponse
import io.sentry.TypeCheckHint.APOLLO_REQUEST
import io.sentry.TypeCheckHint.APOLLO_RESPONSE

Sentry.init { options ->
  // Add a callback that will be used before the event is sent to Sentry.
  // With this callback, you can modify the event or, when returning null, also discard the event.
  options.beforeSend = BeforeSendCallback { event, hint ->
    val request = hint.getAs(APOLLO_REQUEST, HttpRequest::class.java)
    val response = hint.getAs(APOLLO_RESPONSE, HttpResponse::class.java)

    // customize or drop the event
    event
  }
}
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").