Skip to content

Commit

Permalink
inline trace plugin: include query plan and subgraph traces if instal…
Browse files Browse the repository at this point in the history
…led in gateway
  • Loading branch information
glasser committed Jan 20, 2022
1 parent b6cbb51 commit 2850884
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ The version headers in this history reflect the versions of Apollo Server itself
- [`@apollo/gateway`](https://github.com/apollographql/federation/blob/HEAD/gateway-js/CHANGELOG.md)
- [`@apollo/federation`](https://github.com/apollographql/federation/blob/HEAD/federation-js/CHANGELOG.md)

## vNEXT

- `apollo-server-core`: The inline trace plugin will now include the full query plan and subgraph traces if manually installed in an Apollo Gateway. (Previously, you technically could install this plugin in a Gateway but it would not have any real trace data.) This is recommended for development use only and not in production servers. [PR #FIXME](https://github.com/apollographql/apollo-server/pull/FIXME)

## v3.6.2

- ⚠️ **SECURITY** `apollo-server-env`: Update dependency on `node-fetch` to require v2.6.7 rather than v2.6.1. This includes the fix to [CVE-2022-0235](https://nvd.nist.gov/vuln/detail/CVE-2022-0235), a vulnerability where credentials sent along with a request could be sent to a different origin if the fetched URL responds with an attacker-controlled HTTP redirect. This is the default fetcher used by `apollo-datasource-rest`, usage reporting, schema reporting, and `@apollo/gateway` in versions prior to v0.46.0. We do not believe that the way that this is used by usage reporting or schema reporting is vulnerable to the exploit, but if you use `apollo-datasource-rest` in such a way that the servers you talk to might serve a surprising redirect, this upgrade would be helpful. Note that to ensure you're using the appropriate version of `apollo-server-env` with `apollo-datasource-rest`, you need to be using v3.5.1 of that package. (We plan to separate the release process of `apollo-datasource-rest` from Apollo Server soon so that it can have a more reasonable changelog.) If upgrading to this version is challenging, you can also work around this by ensuring that `node-fetch@2.6.7` is the version used in your project, or by specifying a `fetcher` explicitly to your older Gateway, REST datasource, etc.
Expand Down
2 changes: 2 additions & 0 deletions docs/source/api/plugin/inline-trace.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Note that when this plugin is installed in your app, any client can request a tr

(Note: in addition to this plugin (which adds a base64-encoded trace to the `ftv1` extension of responses), the Apollo platform used to have support for an older JSON-based format which added a `tracing` extension to responses. This support was enabled in Apollo Server 2 by passing `tracing: true` to the `ApolloServer` constructor; that option has been removed from Apollo Server 3. That format was designed for use with a no longer supported tool called `engineproxy`, and also is recognized by graphql-playground. That format was more verbose due to its use of JSON and the way that it represented trace node IDs.)

When using Federation, you typically run this plugin in subgraphs and you run the usage reporting plugin in gateways; this is how the default behavior works. If you include this plugin in a gateway, then the gateway will include a full trace including the query plan and all subgraph traces inline in its responses. This is not recommended for publicly exposed servers, but can be helpful when developing locally if you want to see the exact query plan generated by your gateway.

## Options

<table class="field-table">
Expand Down
21 changes: 20 additions & 1 deletion packages/apollo-server-core/src/plugin/inlineTrace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function ApolloServerPluginInlineTrace(
}
}
},
async requestDidStart({ request: { http } }) {
async requestDidStart({ request: { http }, metrics }) {
if (!enabled) {
return;
}
Expand All @@ -67,6 +67,17 @@ export function ApolloServerPluginInlineTrace(
return;
}

// If some other (user-written?) plugin already decided that we are not
// capturing traces, then we should not capture traces.
if (metrics.captureTraces === false) {
return;
}

// Note that this will override any `fieldLevelInstrumentation` parameter
// to the usage reporting plugin for requests with the
// `apollo-federation-include-trace` header set.
metrics.captureTraces = true;

treeBuilder.startTiming();

return {
Expand All @@ -87,6 +98,14 @@ export function ApolloServerPluginInlineTrace(
// If we wait any longer, the time we record won't actually be sent anywhere!
treeBuilder.stopTiming();

// If we're in a gateway, include the query plan (and subgraph traces)
// in the inline trace. This is designed more for manually querying
// your graph while running locally to see what the query planner is
// doing rather than for running in production.
if (metrics.queryPlanTrace) {
treeBuilder.trace.queryPlan = metrics.queryPlanTrace;
}

const encodedUint8Array = Trace.encode(treeBuilder.trace).finish();
const encodedBuffer = Buffer.from(
encodedUint8Array,
Expand Down
48 changes: 29 additions & 19 deletions packages/apollo-server-core/src/plugin/usageReporting/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,25 +515,35 @@ export function ApolloServerPluginUsageReporting<TContext>(
// immediately fail due to unknown operation name.
!graphqlUnknownOperationName
) {
// We're not completely ignoring the operation. But should we
// calculate a detailed trace of every field while we do so (either
// directly in this plugin, or in a subgraph by sending the
// apollo-federation-include-trace header)? That will allow this
// operation to contribute to the "field executions" column in the
// Studio Fields page, to the timing hints in Explorer and
// vscode-graphql, and to the traces visible under Operations. (Note
// that `true` here does not imply that this operation will
// necessarily be *sent* to the usage-reporting endpoint in the form
// of a trace --- it still might be aggregated into stats first. But
// capturing a trace will mean we can understand exactly what fields
// were executed and what their performance was, at the tradeoff of
// some overhead for tracking the trace (and transmitting it between
// subgraph and gateway).
const rawWeight = await fieldLevelInstrumentation(requestContext);
treeBuilder.trace.fieldExecutionWeight =
typeof rawWeight === 'number' ? rawWeight : rawWeight ? 1 : 0;

metrics.captureTraces = !!treeBuilder.trace.fieldExecutionWeight;
if (metrics.captureTraces === undefined) {
// We're not completely ignoring the operation. But should we
// calculate a detailed trace of every field while we do so (either
// directly in this plugin, or in a subgraph by sending the
// apollo-federation-include-trace header)? That will allow this
// operation to contribute to the "field executions" column in the
// Studio Fields page, to the timing hints in Explorer and
// vscode-graphql, and to the traces visible under Operations. (Note
// that `true` here does not imply that this operation will
// necessarily be *sent* to the usage-reporting endpoint in the form
// of a trace --- it still might be aggregated into stats first. But
// capturing a trace will mean we can understand exactly what fields
// were executed and what their performance was, at the tradeoff of
// some overhead for tracking the trace (and transmitting it between
// subgraph and gateway).
const rawWeight = await fieldLevelInstrumentation(
requestContext,
);
treeBuilder.trace.fieldExecutionWeight =
typeof rawWeight === 'number' ? rawWeight : rawWeight ? 1 : 0;

metrics.captureTraces =
!!treeBuilder.trace.fieldExecutionWeight;
} else if (metrics.captureTraces) {
// Some other plugin already decided that we are capturing traces.
// (For example, you may be running ApolloServerPluginInlineTrace
// and this is a request with the header that requests tracing.)
treeBuilder.trace.fieldExecutionWeight = 1;
}
}
},
async executionDidStart() {
Expand Down

0 comments on commit 2850884

Please sign in to comment.