OpenTelemetry in Apollo Server
You can configure your gateway, your individual subgraphs, or even a monolothic Apollo Server instance to emit telemetry related to processing GraphQL operations.
Additionally, the @apollo/gateway
library provides built-in OpenTelemetry instrumentation to emit
Apollo Studio does not currently consume OpenTelemetry-formatted data. To push trace data to Studio, see
You should configure OpenTelemetry if you want to push trace data to an OpenTelemetry-compatible system, such as
Setup
1. Install required libraries
To use OpenTelemetry in your application, you need to install a baseline set of @opentelemetry
Node.js libraries. This set differs slightly depending on whether you're setting up your federated gateway or a subgraph/monolith.
Most importantly, subgraphs and monoliths must install @opentelemetry/instrumentation-graphql
, and gateways must not install it.
As shown, most @opentelemetry
libraries should maintain a consistent version number to prevent dependency conflicts. As of this article's most recent update, the latest version is 0.22
.
Update @apollo/gateway
@apollo/gateway
If you're using OpenTelemetry in your federated gateway, also update the @apollo/gateway
library to version 0.31.1
or later to add support for
2. Configure instrumentation
Next, update your application to configure your OpenTelemetry instrumentation as early as possible in your app's execution. This must occur before you even import apollo-server
, express
, or http
. Otherwise, your trace data will be incomplete.
We recommend putting this configuration in its own file, which you import at the very top of index.js
. A sample file is provided below (note the lines that should either be deleted or uncommented).
// Import required symbolsconst { HttpInstrumentation } = require ('@opentelemetry/instrumentation-http');const { ExpressInstrumentation } = require ('@opentelemetry/instrumentation-express');const { registerInstrumentations } = require('@opentelemetry/instrumentation');const { NodeTracerProvider } = require("@opentelemetry/node");const { SimpleSpanProcessor, ConsoleSpanExporter } = require ("@opentelemetry/tracing");const { Resource } = require('@opentelemetry/resources');// **DELETE IF SETTING UP A GATEWAY, UNCOMMENT OTHERWISE**// const { GraphQLInstrumentation } = require ('@opentelemetry/instrumentation-graphql');// Register server-related instrumentationregisterInstrumentations({instrumentations: [new HttpInstrumentation(),new ExpressInstrumentation(),// **DELETE IF SETTING UP A GATEWAY, UNCOMMENT OTHERWISE**//new GraphQLInstrumentation()]});// Initialize provider and identify this particular service// (in this case, we're implementing a federated gateway)const provider = new NodeTracerProvider({resource: Resource.default().merge(new Resource({// Replace with any string to identify this service in your system"service.name": "gateway",})),});// Configure a test exporter to print all traces to the consoleconst consoleExporter = new ConsoleSpanExporter();provider.addSpanProcessor(new SimpleSpanProcessor(consoleExporter));// Register the provider to begin tracingprovider.register();
For now, this code does not push trace data to an external system. Instead, it prints that data to the console for debugging purposes.
After you make these changes to your app, start it up locally. It should begin printing trace data similar to the following:
Nice! Next, we can modify this code to begin pushing trace data to an external service, such as Zipkin or Jaeger.
3. Push trace data to a tracing system
Next, let's modify the code in the
To run Zipkin locally,
First, we need to replace our ConsoleSpanExporter
(which prints traces to the terminal) with a ZipkinExporter
, which specifically pushes trace data to a running Zipkin instance.
Install the following additional library:
npm install @opentelemetry/exporter-zipkin@0.22
Then, import the ZipkinExporter
in your dedicated OpenTelemetry file:
const { ZipkinExporter } = require("@opentelemetry/exporter-zipkin");
Now we can replace our ConsoleSpanExporter
with a ZipkinExporter
. Replace lines 27-21 of the code in
// Configure an exporter that pushes all traces to Zipkin// (This assumes Zipkin is running on localhost at the// default port of 9411)const zipkinExporter = new ZipkinExporter({// url: set_this_if_not_running_zipkin_locally});provider.addSpanProcessor(new SimpleSpanProcessor(zipkinExporter));
Now, open Zipkin in your browser at http://localhost:9411
. You should now be able to query recent trace data in the UI!
You can show the details of any operation and see a breakdown of its processing timeline by span.
4. Update for production readiness
Our example telemetry configuration assumes that Zipkin is running locally, and that we want to process every span individually as it's emitted.
To prepare for production, we probably at least want to conditionally set the url
option for the ZipkinExporter
and replace our SimpleSpanProcessor
with a BatchSpanProcessor
.
You can learn more about the settings available to these and other OpenTelemetry objects in the
GraphQL-specific spans
The @opentelemetry/instrumentation-graphql
library enables subgraphs and monoliths to emit the following spans as part of
Reminder: Federated gateways must not install the @opentelemetry/instrumentation-graphql
library, so these spans are not included in its traces.
Name | Description |
---|---|
graphql.parse | The amount of time the server spent parsing an operation string. |
graphql.validate | The amount of time the server spent validating an operation string. |
graphql.execute | The total amount of time the server spent executing an operation. |
graphql.resolve | The amount of time the server spent resolving a particular field. |
Note that not every GraphQL span appears in every operation trace. This is because Apollo server can skip parsing or validating an operation string if that string is available in the operation cache.
Gateway-specific spans
The @apollo/gateway
library emits the following spans as part of
Name | Description |
---|---|
gateway.request | The total amount of time the gateway spent serving a request. |
gateway.validate | The amount of time the gateway spent validating a GraphQL operation string. |
gateway.plan | The amount of time the gateway spent generating a query plan for a validated operation. |
gateway.execute | The amount of time the gateway spent executing operations on subgraphs. |
gateway.fetch | The amount of time the gateway spent fetching data from a particular subgraph. |
gateway.postprocessing | The amount of time the gateway spent composing a complete response from individual subgraph responses. |