GraphQL

Guidelines for GraphQL client integrations — error capture, performance instrumentation, breadcrumbs, and server/client differences.

Statusstable
Version1.0.0(changelog)

GraphQL client integrations should match the guidelines for HTTP Client Integrations with differences described below.


Stablespecified since 1.0.0

The failedRequestStatusCodes parameter does not exist because GraphQL errors are not HTTP errors, so that the request can be errored even though the HTTP status code of the response is successful.

Instead, the error has to be captured if the GraphQL response contains an errors array. This can be done by regexing the response body, e.g.:

Copied
val regex = "(?i)\"errors\"\\s*:\\s*\\[".toRegex()

// [body] is the stringified GraphQL response body
if (regex.containsMatchIn(body)) {
    // captures the error
}

Additional fields for breadcrumbs:

  • data (all fields are optional but recommended):
    • operation_name - The GraphQL operation name
    • operation_type - The GraphQL operation type, i.e: query, mutation, subscription
    • operation_id - The GraphQL operation ID

Required fields for the Request interface:

Copied
{
  "request": {
    "api_target": "graphql",
    "data": {
      "foo": "bar"
    }
  }
}

The data field is a JSON object that contains the GraphQL request payload.

Required fields for the Response interface:

Copied
{
  "contexts": {
    "response": {
      "data": {
        "foo": "bar"
      }
    }
  }
}

The data field is a JSON object that contains the GraphQL response payload. Attaching request and response bodies should be guarded by sendDefaultPii and/or another flag to opt-in (e.g. captureFailedRequests).

Required fields for the Event interface:

The fingerprints field should be set to ["$operationName", "$operationType", "$statusCode"].

Copied
{
  "fingerprints": ["$operationName", "$operationType", "$statusCode"]
}
Stablespecified since 1.0.0

The GraphQL Performance integration should match the guidelines for GraphQL Client Error Capture with a few differences:

The transaction's name should be set with the GraphQL operation name, if possible, otherwise fallback to something unique that makes sense, e.g. the canonical name of the actual/generated class.

The transaction's description should be set with the GraphQL operation name, operation type (query, mutation or subscription) and status code, if possible.

The request.api_target should be set with graphql.

The request.data should be set with the raw GraphQL request payload, if possible. This should be guarded by an opt-in flag, e.g. sendDefaultPii.

The contexts.response.data should be set with the raw GraphQL response payload, only if there were errors. This should be guarded by an opt-in flag, e.g. sendDefaultPii and maxResponseBodySize.

Some frameworks may use a Stream object for the response, in this case, the object can't be consumed twice, so the SDK should try check and clone the object, if possible.

Spans should be created for resolvers, if possible. These are sometimes also called data fetchers.

Spans should be created for data loaders, if possible.

The operation type should follow the Span Operation Conventions.

Extra (data) attributes for transactions and/or spans, there are Span Data and OTel GraphQL conventions.

Instrumenting APM for GraphQL will depend on the instrumented GraphQL library, if there are available hooks for it, the SDK should use them, otherwise, the SDK could try to monkeypatch the library or instrument the transport layer using heuristics, for example, if the URL ends with graphql, if there are HTTP Headers, etc.

If there are hooks available and the transport layer is also instrumented (e.g. Apollo Interceptors for GraphQL and Spring), the SDK should give preference to the layer that has more information and avoid creating duplicate transactions/spans, or merge the information, if possible.

Spring GraphQL has its own observation package.

GraphQL Java has its own instrumentation package.

Apollo GraphQL has its own tracing extensions, in this case it'd even be possible to create synthetic transactions and spans out of the tracing extension.

Changes in the product may be necessary, e.g. if request.api_target is set to graphql, the request.data and contexts.response.data should do syntax highlighting.

Performance issues can be created for GraphQL transactions and spans, for example, N+1, query complexity, etc.

Stablespecified since 1.0.0

GraphQL Performance for Clients is very similar to the implementation for Servers, the difference is that you'll create a span instead of a transaction.

Spans don't contain the request and response interfaces, but set the span description similarly to the transaction description.

Stablespecified since 1.0.0

Breadcrumbs should be added for each GraphQL operation (resolvers, data loaders, etc), if possible.

The Breadcrumb type should be graphql and the category should be the operation type, otherwise graphql.operation if not available.

Additional fields for breadcrumbs:

  • data (all fields are optional but recommended):
    • operation_name - The GraphQL operation name
    • operation_type - The GraphQL operation type, i.e: query, mutation, subscription
    • operation_id - The GraphQL operation ID

Avoid setting the query String as part of the data field since the event can be dropped due to size limit.

In case more additional fields are needed, the data field can be used to add more context, e.g. graphql.path, graphql.field, graphql.type, etc.

The category can also be adapted to its own type, e.g. graphql.resolver, graphql.data_loader, etc.

For resolvers or data fetchers a breadcrumb could have the following fields:

  • type = graphql
  • category = graphql.fetcher
  • path - Path in the query, e.g. project/status
  • field - Field being fetched, e.g. status
  • type - Type being fetched, e.g. String
  • object_type - Object type being fetched, e.g. Project

For data loaders a breadcrumb could have the following fields:

  • type = graphql
  • category = graphql.data_loader
  • keys - Keys that should be loaded by the data loader
  • key_type - Type of the key
  • value_type - Type of the value
  • name - Name of the data loader

If there are hooks available and the transport layer is also instrumented (e.g. Apollo Interceptors for GraphQL and Spring), the SDK should give preference to the layer that has more information and avoid creating duplicate breadcrumbs, or merge the information, if possible.


VersionDateSummary
1.0.02025-02-24Initial spec, extracted from expected features
Was this helpful?
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").