Skip to content

Commit b1548c1

Browse files
glasserDanielle Mantrevor-scheer
authored
Don't install the usage reporting plugin by default in subgraphs (#7184)
The usage reporting plugin is designed for monolithic graphs and Gateways, not for subgraphs. You will get strange results in GraphOS/Studio if you send usage reports from a subgraph to Studio. This PR changes the defaults so that subgraphs do not automatically install the usage reporting plugin even if API key and graph ref are provided; in this case, a warning is logged. You still can explicitly install ApolloServerPluginUsageReporting in subgraphs, though a warning will be logged. Fixes #7121. Also, update terminology around subgraphs. Various log messages and internal symbol names and comments referred to "federated service" rather than the newer and clearer "subgraph". Co-authored-by: Danielle Man <[email protected]> Co-authored-by: Trevor Scheer <[email protected]>
1 parent 46af825 commit b1548c1

File tree

10 files changed

+123
-62
lines changed

10 files changed

+123
-62
lines changed

.changeset/rude-steaks-smell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@apollo/server': minor
3+
---
4+
5+
Don't automatically install the usage reporting plugin in servers that appear to be hosting a federated subgraph (based on the existence of a field `_Service.sdl: String`). This is generally a misconfiguration. If an API key and graph ref are provided to the subgraph, log a warning and do not enable the usage reporting plugin. If the usage reporting plugin is explicitly installed in a subgraph, log a warning but keep it enabled.

docs/source/api/plugin/usage-reporting.mdx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,20 @@ api_reference: true
55

66
Apollo Server's built-in usage reporting plugin gathers data on how your clients use the operations and fields in your GraphQL schema. The plugin also handles pushing this usage data to [Apollo Studio](/studio/), as described in [Metrics and logging](../../monitoring/metrics/).
77

8+
This plugin is designed to be used in an Apollo Gateway or in a monolithic server; it is not designed to be used from a subgraph. In a supergraph running Apollo Federation, the Apollo Gateway or Apollo Router will send usage reports to Apollo's cloud. Subgraphs don't need to also send usage reports to Apollo's cloud; instead, they send it to the Router via [inline traces](./inline-trace/) and the Router combines execution information across all subgraphs and sends summarized reports to the cloud.
9+
810
## Default installation
911

1012
> 📣 **New in Apollo Server 4**: error details are not included in traces by default. For more details, see [Error Handling](../../data/errors/#masking-and-logging-errors).
1113
12-
Apollo Server automatically installs and enables this plugin with default settings if you [provide a graph API key and a graph ref to Apollo Server](../../monitoring/metrics/#connecting-to-apollo-studio). You usually do this by setting the `APOLLO_KEY` and `APOLLO_GRAPH_REF` (or `APOLLO_GRAPH_ID` and `APOLLO_GRAPH_VARIANT`) environment variables. No other action is required.
14+
Apollo Server automatically installs and enables this plugin with default settings if you [provide a graph API key and a graph ref to Apollo Server](../../monitoring/metrics/#connecting-to-apollo-studio) and your server is not a federated subgraph. You usually do this by setting the `APOLLO_KEY` and `APOLLO_GRAPH_REF` (or `APOLLO_GRAPH_ID` and `APOLLO_GRAPH_VARIANT`) environment variables. No other action is required.
1315

14-
If you don't provide an API key and graph ref, this plugin is not installed.
16+
If you don't provide an API key and graph ref, or if your server is a federated subgraph, this plugin is not automatically installed.
1517

1618
If you provide an API key but _don't_ provide a graph ref, a warning is logged. You can [disable the plugin](#disabling-the-plugin) to hide the warning.
1719

20+
If you provide an API key and graph ref but your server _is_ a federated subgraph, a warning is logged. You can [disable the plugin](#disabling-the-plugin) to hide the warning.
21+
1822
## Custom installation
1923

2024
If you want to configure the usage reporting plugin, import it and pass it to your `ApolloServer` constructor's `plugins` array:
@@ -38,6 +42,8 @@ const server = new ApolloServer({
3842

3943
</MultiCodeBlock>
4044

45+
> While you can install the usage reporting plugin in a server that is a federated subgraph, this is not recommended, and a warning will be logged.
46+
4147
Supported configuration options are listed below.
4248

4349
#### Options
@@ -529,4 +535,4 @@ const server = new ApolloServer({
529535

530536
</MultiCodeBlock>
531537

532-
This also disables the warning log if you provide an API key but do not provide a graph ref.
538+
This also disables the warning log if you provide an API key but do not provide a graph ref, or if you provide an API key and graph ref and your server is a federated subgraph.

docs/source/migration.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,6 +1743,15 @@ new ApolloServer({
17431743

17441744
(As [described above](#rewriteerror-plugin-option), the `rewriteError` option has been replaced by a `transform` option on `sendErrors` or `includeErrors`.)
17451745

1746+
1747+
### Usage reporting plugin is off by default on subgraphs
1748+
1749+
In an Apollo Federation supergraph, your Apollo Gateway or Apollo Router sends [usage reports](./api/plugin/usage-reporting/) to Apollo's servers; information about what happens inside individual subgraph servers is sent from the subgraphs to the Gateway or Router via [inline traces](./api/plugin/inline-trace/). That is to say: the usage reporting plugin is *not* designed for use in federated subgraphs.
1750+
1751+
In Apollo Server 3, if you provide an Apollo API key and graph ref and do not explicitly install the `ApolloServerPluginUsageReporting` or `ApolloServerPluginUsageReportingDisabled` plugins, the `ApolloServerPluginUsageReporting` plugin will be installed with its default configuration, even if the server is a subgraph.
1752+
1753+
In Apollo Server 4, this automatic installation does not occur in federated subgraphs. You still can explicitly install `ApolloServerPluginUsageReporting` in your subgraph, though this is not recommended and a warning will be logged.
1754+
17461755
## Renamed packages
17471756

17481757
The following packages have been renamed in Apollo Server 4:

packages/server/src/ApolloServer.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,11 @@ export class ApolloServer<in out TContext extends BaseContext = BaseContext> {
869869
const { ApolloServerPluginUsageReporting } = await import(
870870
'./plugin/usageReporting/index.js'
871871
);
872-
plugins.unshift(ApolloServerPluginUsageReporting());
872+
plugins.unshift(
873+
ApolloServerPluginUsageReporting({
874+
__onlyIfSchemaIsNotSubgraph: true,
875+
}),
876+
);
873877
} else {
874878
this.logger.warn(
875879
'You have specified an Apollo key but have not specified a graph ref; usage ' +
@@ -917,7 +921,7 @@ export class ApolloServer<in out TContext extends BaseContext = BaseContext> {
917921
'./plugin/inlineTrace/index.js'
918922
);
919923
plugins.push(
920-
ApolloServerPluginInlineTrace({ __onlyIfSchemaIsFederated: true }),
924+
ApolloServerPluginInlineTrace({ __onlyIfSchemaIsSubgraph: true }),
921925
);
922926
}
923927
}

packages/server/src/plugin/inlineTrace/index.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Trace } from '@apollo/usage-reporting-protobuf';
22
import { TraceTreeBuilder } from '../traceTreeBuilder.js';
33
import type { SendErrorsOptions } from '../usageReporting/index.js';
44
import { internalPlugin } from '../../internalPlugin.js';
5-
import { schemaIsFederated } from '../schemaIsFederated.js';
5+
import { schemaIsSubgraph } from '../schemaIsSubgraph.js';
66
import type { ApolloServerPlugin } from '../../externalTypes/index.js';
77

88
export interface ApolloServerPluginInlineTraceOptions {
@@ -29,14 +29,14 @@ export interface ApolloServerPluginInlineTraceOptions {
2929
/**
3030
* This option is for internal use by `@apollo/server` only.
3131
*
32-
* By default we want to enable this plugin for federated schemas only, but we
32+
* By default we want to enable this plugin for subgraph schemas only, but we
3333
* need to come up with our list of plugins before we have necessarily loaded
3434
* the schema. So (unless the user installs this plugin or
35-
* ApolloServerPluginInlineTraceDisabled themselves), `@apollo/server`
36-
* always installs this plugin and uses this option to make sure traces are
37-
* only included if the schema appears to be federated.
35+
* ApolloServerPluginInlineTraceDisabled themselves), `@apollo/server` always
36+
* installs this plugin and uses this option to make sure traces are only
37+
* included if the schema appears to be a subgraph.
3838
*/
39-
__onlyIfSchemaIsFederated?: boolean;
39+
__onlyIfSchemaIsSubgraph?: boolean;
4040
}
4141

4242
// This ftv1 plugin produces a base64'd Trace protobuf containing only the
@@ -47,7 +47,7 @@ export interface ApolloServerPluginInlineTraceOptions {
4747
export function ApolloServerPluginInlineTrace(
4848
options: ApolloServerPluginInlineTraceOptions = Object.create(null),
4949
): ApolloServerPlugin {
50-
let enabled: boolean | null = options.__onlyIfSchemaIsFederated ? null : true;
50+
let enabled: boolean | null = options.__onlyIfSchemaIsSubgraph ? null : true;
5151
return internalPlugin({
5252
__internal_plugin_id__: 'InlineTrace',
5353
__is_disabled_plugin__: false,
@@ -57,10 +57,10 @@ export function ApolloServerPluginInlineTrace(
5757
// like the log line, just install `ApolloServerPluginInlineTrace()` in
5858
// `plugins` yourself.
5959
if (enabled === null) {
60-
enabled = schemaIsFederated(schema);
60+
enabled = schemaIsSubgraph(schema);
6161
if (enabled) {
6262
logger.info(
63-
'Enabling inline tracing for this federated service. To disable, use ' +
63+
'Enabling inline tracing for this subgraph. To disable, use ' +
6464
'ApolloServerPluginInlineTraceDisabled.',
6565
);
6666
}

packages/server/src/plugin/schemaIsFederated.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { GraphQLSchema, isObjectType, isScalarType } from 'graphql';
2+
3+
// Returns true if it appears that the schema was appears to be of a subgraph
4+
// (eg, returned from @apollo/subgraph's buildSubgraphSchema). This strategy
5+
// avoids depending explicitly on @apollo/subgraph or relying on something that
6+
// might not survive transformations like monkey-patching a boolean field onto
7+
// the schema.
8+
//
9+
// This is used for two things:
10+
// 1) Determining whether traces should be added to responses if requested with
11+
// an HTTP header. If you want to include these traces even for non-subgraphs
12+
// (when requested via header, eg for Apollo Explorer's trace view) you can
13+
// use ApolloServerPluginInlineTrace explicitly; if you want to never include
14+
// these traces even for subgraphs you can use
15+
// ApolloServerPluginInlineTraceDisabled.
16+
// 2) Determining whether schema-reporting should be allowed; subgraphs cannot
17+
// report schemas, and we accordingly throw if it's attempted.
18+
export function schemaIsSubgraph(schema: GraphQLSchema): boolean {
19+
const serviceType = schema.getType('_Service');
20+
if (!isObjectType(serviceType)) {
21+
return false;
22+
}
23+
const sdlField = serviceType.getFields().sdl;
24+
if (!sdlField) {
25+
return false;
26+
}
27+
const sdlFieldType = sdlField.type;
28+
if (!isScalarType(sdlFieldType)) {
29+
return false;
30+
}
31+
return sdlFieldType.name == 'String';
32+
}

packages/server/src/plugin/schemaReporting/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { internalPlugin } from '../../internalPlugin.js';
33
import { v4 as uuidv4 } from 'uuid';
44
import { printSchema, validateSchema, buildSchema } from 'graphql';
55
import { SchemaReporter } from './schemaReporter.js';
6-
import { schemaIsFederated } from '../schemaIsFederated.js';
6+
import { schemaIsSubgraph } from '../schemaIsSubgraph.js';
77
import type { SchemaReport } from './generated/operations.js';
88
import type { ApolloServerPlugin } from '../../externalTypes/index.js';
99
import type { Fetcher } from '@apollo/utils.fetcher';
@@ -108,12 +108,12 @@ export function ApolloServerPluginSchemaReporting(
108108
}
109109
}
110110

111-
if (schemaIsFederated(schema)) {
111+
if (schemaIsSubgraph(schema)) {
112112
throw Error(
113113
[
114-
'Schema reporting is not yet compatible with federated services.',
115-
"If you're interested in using schema reporting with federated",
116-
'services, please contact Apollo support. To set up managed federation, see',
114+
'Schema reporting is not yet compatible with Apollo Federation subgraphs.',
115+
"If you're interested in using schema reporting with subgraphs,",
116+
'please contact Apollo support. To set up managed federation, see',
117117
'https://go.apollo.dev/s/managed-federation',
118118
].join(' '),
119119
);

packages/server/src/plugin/usageReporting/options.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,17 @@ export interface ApolloServerPluginUsageReportingOptions<
367367
* about how the signature relates to the operation you executed.
368368
*/
369369
calculateSignature?: (ast: DocumentNode, operationName: string) => string;
370+
/**
371+
* This option is for internal use by `@apollo/server` only.
372+
*
373+
* By default we want to enable this plugin for non-subgraph schemas only, but
374+
* we need to come up with our list of plugins before we have necessarily
375+
* loaded the schema. So (unless the user installs this plugin or
376+
* ApolloServerPluginUsageReportingDisabled themselves), `@apollo/server`
377+
* always installs this plugin (if API key and graph ref are provided) and
378+
* uses this option to disable usage reporting if the schema is a subgraph.
379+
*/
380+
__onlyIfSchemaIsNotSubgraph?: boolean;
370381
//#endregion
371382
}
372383

packages/server/src/plugin/usageReporting/plugin.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { makeTraceDetails } from './traceDetails.js';
3939
import { packageVersion } from '../../generated/packageVersion.js';
4040
import { computeCoreSchemaHash } from '../../utils/computeCoreSchemaHash.js';
4141
import type { HeaderMap } from '../../utils/HeaderMap.js';
42+
import { schemaIsSubgraph } from '../schemaIsSubgraph.js';
4243

4344
const gzipPromise = promisify(gzip);
4445

@@ -70,9 +71,11 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
7071
? fieldLevelInstrumentationOption
7172
: async () => true;
7273

73-
let requestDidStartHandler: (
74-
requestContext: GraphQLRequestContext<TContext>,
75-
) => GraphQLRequestListener<TContext>;
74+
let requestDidStartHandler:
75+
| ((
76+
requestContext: GraphQLRequestContext<TContext>,
77+
) => GraphQLRequestListener<TContext>)
78+
| null = null;
7679
return internalPlugin({
7780
__internal_plugin_id__: 'UsageReporting',
7881
__is_disabled_plugin__: false,
@@ -81,20 +84,19 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
8184
// this little hack. (Perhaps we should also allow GraphQLServerListener to contain
8285
// a requestDidStart?)
8386
async requestDidStart(requestContext: GraphQLRequestContext<TContext>) {
84-
if (!requestDidStartHandler) {
85-
throw Error(
86-
'The usage reporting plugin has been asked to handle a request before the ' +
87-
'server has started. See https://github.com/apollographql/apollo-server/issues/4588 ' +
88-
'for more details.',
89-
);
87+
if (requestDidStartHandler) {
88+
return requestDidStartHandler(requestContext);
9089
}
91-
return requestDidStartHandler(requestContext);
90+
// This happens if usage reporting is disabled (eg because this is a
91+
// subgraph).
92+
return {};
9293
},
9394

9495
async serverWillStart({
9596
logger: serverLogger,
9697
apollo,
9798
startedInBackground,
99+
schema,
98100
}): Promise<GraphQLServerListener> {
99101
// Use the plugin-specific logger if one is provided; otherwise the general server one.
100102
const logger = options.logger ?? serverLogger;
@@ -108,6 +110,31 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
108110
);
109111
}
110112

113+
if (schemaIsSubgraph(schema)) {
114+
if (options.__onlyIfSchemaIsNotSubgraph) {
115+
logger.warn(
116+
'You have specified an Apollo API key and graph ref but this server appears ' +
117+
'to be a subgraph. Typically usage reports are sent to Apollo by your Router ' +
118+
'or Gateway, not directly from your subgraph; usage reporting is disabled. To ' +
119+
'enable usage reporting anyway, explicitly install `ApolloServerPluginUsageReporting`. ' +
120+
'To disable this warning, install `ApolloServerPluginUsageReportingDisabled`.',
121+
);
122+
// This early return means we don't start background timers, don't
123+
// register serverDidStart, don't assign requestDidStartHandler, etc.
124+
return {};
125+
} else {
126+
// This is just a warning; usage reporting is still enabled. If it
127+
// turns out there are lots of people who really need to have this odd
128+
// setup and they don't like the warning, we can provide a new option
129+
// to disable the warning (or they can filter in their `logger`).
130+
logger.warn(
131+
'You have installed `ApolloServerPluginUsageReporting` but this server appears to ' +
132+
'be a subgraph. Typically usage reports are sent to Apollo by your Router ' +
133+
'or Gateway, not directly from your subgraph.',
134+
);
135+
}
136+
}
137+
111138
logger.info(
112139
'Apollo usage reporting starting! See your graph at ' +
113140
`https://studio.apollographql.com/graph/${encodeURI(graphRef)}/`,

0 commit comments

Comments
 (0)