-
Notifications
You must be signed in to change notification settings - Fork 254
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Release @apollo/[email protected] (#845)
* Add OpenTelemetry support This PR adds instrumentation to emit the following spans: federation.request - total time to server a request. federation.validate - time to validate the federated query. federation.plan - time to plan the federated query. federation.execute - the total time executing query. federation.fetch - time fetching data from a subgraph. federation.postprocessing - time to render the response from all the subgraph data. The instrumentation looks a little messy due to the requirement to set error status on spans on exception, but also if errors are added to the response. It is a reissue of the following PRs #803 #828 * WIP on opentelemetry docs * First run at OT docs ready for review * Incorporate feedback from @BrynCooke * Add note to update @apollo/gateway * Release - [email protected] - @apollo/[email protected] - @apollo/[email protected] * Update changelog for publish Co-authored-by: bryncooke <[email protected]> Co-authored-by: Stephen Barlow <[email protected]> Co-authored-by: Stephen Barlow <[email protected]>
- Loading branch information
Showing
15 changed files
with
933 additions
and
241 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
--- | ||
title: Federated traces | ||
title: Federated trace data | ||
description: How federated tracing works | ||
--- | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
--- | ||
title: OpenTelemetry in Apollo Server | ||
sidebar_title: OpenTelemetry | ||
--- | ||
|
||
import { | ||
ExpansionPanel, | ||
} from 'gatsby-theme-apollo-docs/src/components/expansion-panel'; | ||
|
||
[OpenTelemetry](https://opentelemetry.io/) is a collection of open-source tools for generating and processing telemetry data (such as logs and metrics) from different systems in a generic, consistent way. | ||
|
||
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 [gateway-specific spans](#gateway-specific-spans) for operation traces. | ||
|
||
> Apollo Studio does _not_ currently consume OpenTelemetry-formatted data. To push trace data to Studio, see [Federated trace data](./metrics/). | ||
> | ||
> You should configure OpenTelemetry if you want to push trace data to an OpenTelemetry-compatible system, such as [Zipkin](https://zipkin.io/) or [Jaeger](https://www.jaegertracing.io/). | ||
## 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. | ||
|
||
<ExpansionPanel title="Gateway libraries"> | ||
|
||
```bash | ||
npm install \ | ||
@opentelemetry/api \ | ||
@opentelemetry/[email protected] \ | ||
@opentelemetry/[email protected] \ | ||
@opentelemetry/[email protected] \ | ||
@opentelemetry/[email protected] | ||
``` | ||
|
||
</ExpansionPanel> | ||
|
||
<ExpansionPanel title="Subgraph/monolith libraries"> | ||
|
||
```bash | ||
npm install \ | ||
@opentelemetry/api \ | ||
@opentelemetry/[email protected] \ | ||
@opentelemetry/[email protected] \ | ||
@opentelemetry/[email protected] \ | ||
@opentelemetry/[email protected] \ | ||
@opentelemetry/[email protected] | ||
``` | ||
|
||
</ExpansionPanel> | ||
|
||
**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` | ||
|
||
If you're using OpenTelemetry in your federated gateway, also update the `@apollo/gateway` library to version `0.31.0` or later to add support for [gateway-specific spans](#gateway-specific-spans). | ||
|
||
### 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). | ||
|
||
```js:title=open-telemetry.js | ||
// Import required symbols | ||
const { 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 instrumentation | ||
registerInstrumentations({ | ||
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 console | ||
const consoleExporter = new ConsoleSpanExporter(); | ||
provider.addSpanProcessor( | ||
new SimpleSpanProcessor(consoleExporter) | ||
); | ||
|
||
// Register the provider to begin tracing | ||
provider.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: | ||
|
||
<ExpansionPanel title="Click to expand"> | ||
|
||
```js | ||
{ | ||
traceId: '0ed36c42718622cc726a661a3328aa61', | ||
parentId: undefined, | ||
name: 'HTTP POST', | ||
id: '36c6a3ae19563ec3', | ||
kind: 1, | ||
timestamp: 1624650903925787, | ||
duration: 26793, | ||
attributes: { | ||
'http.url': 'http://localhost:4000/', | ||
'http.host': 'localhost:4000', | ||
'net.host.name': 'localhost', | ||
'http.method': 'POST', | ||
'http.route': '', | ||
'http.target': '/', | ||
'http.user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36', | ||
'http.request_content_length_uncompressed': 1468, | ||
'http.flavor': '1.1', | ||
'net.transport': 'ip_tcp', | ||
'net.host.ip': '::1', | ||
'net.host.port': 4000, | ||
'net.peer.ip': '::1', | ||
'net.peer.port': 39722, | ||
'http.status_code': 200, | ||
'http.status_text': 'OK' | ||
}, | ||
status: { code: 1 }, | ||
events: [] | ||
} | ||
|
||
{ | ||
traceId: '0ed36c42718622cc726a661a3328aa61', | ||
parentId: '36c6a3ae19563ec3', | ||
name: 'middleware - <anonymous>', | ||
id: '3776786d86f24124', | ||
kind: 0, | ||
timestamp: 1624650903934147, | ||
duration: 63, | ||
attributes: { | ||
'http.route': '/', | ||
'express.name': '<anonymous>', | ||
'express.type': 'middleware' | ||
}, | ||
status: { code: 0 }, | ||
events: [] | ||
} | ||
``` | ||
|
||
</ExpansionPanel> | ||
|
||
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 [previous step](#2-configure-instrumentation) to instead push traces to a locally running instance of [Zipkin](https://zipkin.io/). | ||
|
||
> To run Zipkin locally, [see the quickstart](https://zipkin.io/pages/quickstart.html). If you want to use a different tracing system, consult the documentation for that system. | ||
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: | ||
|
||
```bash | ||
npm install @opentelemetry/[email protected] | ||
``` | ||
|
||
Then, import the `ZipkinExporter` in your dedicated OpenTelemetry file: | ||
|
||
```js:title=open-telemetry.js | ||
const { ZipkinExporter } = require("@opentelemetry/exporter-zipkin"); | ||
``` | ||
|
||
Now we can replace our `ConsoleSpanExporter` with a `ZipkinExporter`. Replace lines 27-21 of the code in [the previous step](#2-configure-instrumentation) with the following: | ||
|
||
```js | ||
// 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 [getting started guide](https://github.com/open-telemetry/opentelemetry-js/tree/main/getting-started). | ||
|
||
## GraphQL-specific spans | ||
|
||
The `@opentelemetry/instrumentation-graphql` library enables subgraphs and monoliths to emit the following spans as part of [OpenTelemetry traces](https://opentelemetry.io/docs/concepts/data-sources/#traces): | ||
|
||
> **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 gateway 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 [OpenTelemetry traces](https://opentelemetry.io/docs/concepts/data-sources/#traces): | ||
|
||
| 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. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
federation-integration-testsuite-js/src/snapshotSerializers/readableSpanArraySerializer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { Plugin } from 'pretty-format'; | ||
import { ReadableSpan } from '@opentelemetry/tracing' | ||
|
||
export default { | ||
test(value: any) { | ||
return value && Array.isArray(value) && value.length > 0 && isReadableSpan(value[0]); | ||
}, | ||
|
||
print(spans: ReadableSpan[]) { | ||
// This method takes an array of spans and builds up a set of trees containing only the information we are interested in. | ||
// Spans contain the ID of it's parent, unless it is a root span with no parent. | ||
// | ||
// The simplified data model is called a span mirror. | ||
// | ||
// The algorithm is: | ||
// 1. create span mirrors for every span and place it in a map. | ||
// For each span: | ||
// 1. find the corresponding mirrors for itself and parent. | ||
// 2. push the parent in to the parent's children array. | ||
|
||
const root = {children:[]}; | ||
const spanMirrors = new Map<ReadableSpan | undefined, any>(); | ||
spanMirrors.set(undefined, root); | ||
spans.forEach((s) => spanMirrors.set(s, {name: s.name, attributes:s.attributes, children: [], status: s.status})); | ||
spans.forEach((s) => { | ||
const spanMirror = spanMirrors.get(s); | ||
const parentSpan = spans.find((s2) => s2.spanContext().spanId === s.parentSpanId); | ||
const parentSpanMirror = spanMirrors.get(parentSpan); | ||
parentSpanMirror.children.push(spanMirror); | ||
}); | ||
|
||
return JSON.stringify(root.children, undefined, 2); | ||
}, | ||
} as Plugin; | ||
|
||
function isReadableSpan(arg: any): arg is ReadableSpan { | ||
let isSpan = arg && 'kind' in arg && 'startTime' in arg && 'parentSpanId' in arg; | ||
return isSpan; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "@apollo/gateway", | ||
"version": "0.30.0", | ||
"version": "0.31.0", | ||
"description": "Apollo Gateway", | ||
"author": "Apollo <[email protected]>", | ||
"main": "dist/index.js", | ||
|
@@ -40,6 +40,7 @@ | |
"pretty-format": "^26.0.0" | ||
}, | ||
"peerDependencies": { | ||
"@opentelemetry/api": "^1.0.0", | ||
"graphql": "^14.5.0 || ^15.0.0" | ||
} | ||
} |
Oops, something went wrong.