Skip to content

Commit 596b4f6

Browse files
committed
usage reporting: add sendTraces, rename sendErrorsInTraces, etc
- We've decided to leave the usage reporting defaults in AS4 as "field level instrumentation on for all operations, send some operations as traces". But we'd like to at least provide a non-experimental mechanism for entirely disabling sending traces to Studio. So there's a new ApolloServerPluginUsageReporting: `sendTraces: false`. - Rename the (new in AS4) usage reporting option `sendErrorsInTraces` to `sendErrors`, because it does also affect error statistics in stats reports if your `transform` function returns `null`. - Remove Apollo-internal `internal_includeTracesContributingToStats` option. This enabled some internal consistency monitoring which we are no longer paying attention to. - If you enable `debugPrintReports`, send the reports as `info` rather than `warn`, which primarily lets us delete some large comments (and makes sense because these debug messages are not warnings). Fixes #6051. Fixes #6078.
1 parent 3a7ba05 commit 596b4f6

File tree

11 files changed

+144
-89
lines changed

11 files changed

+144
-89
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/server": patch
3+
---
4+
5+
New usage reporting option `sendTraces: false` to only send usage reports as aggregated statistics, not per-request traces.

.changeset/many-plums-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/server": patch
3+
---
4+
5+
Remove Apollo-internal `internal_includeTracesContributingToStats`. This should not have been used other than inside Apollo's own servers.

.changeset/short-lies-kneel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/server": patch
3+
---
4+
5+
The usage reporting option `debugPrintReports` now displays reports via `logger.info` rather than `logger.warn`.

.changeset/silly-icons-taste.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@apollo/server-integration-testsuite": patch
3+
"@apollo/server": patch
4+
---
5+
6+
Rename usage reporting option `sendErrorsInTraces` (added in 4.0.0-alpha.4) to `sendErrors`, as it also affects error statistics outside of traces.

docs/source/data/errors.mdx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ You can use Apollo Studio to analyze your server's error rates. By default, the
438438

439439
If you _do_ want error information sent to Studio, you can send every error, or you can modify or redact specific errors before they're transmitted.
440440

441-
To send all errors to Studio you can pass `{ unmodified: true }` to `sendErrorsInTraces`, like so:
441+
To send all errors to Studio you can pass `{ unmodified: true }` to `sendErrors`, like so:
442442

443443
```ts {7}
444444
new ApolloServer({
@@ -447,23 +447,23 @@ new ApolloServer({
447447
ApolloServerPluginUsageReporting({
448448
// If you pass unmodified: true to the usage reporting
449449
// plugin, Apollo Studio receives ALL error details
450-
sendErrorsInTraces: { unmodified: true },
450+
sendErrors: { unmodified: true },
451451
}),
452452
],
453453
});
454454
```
455455

456-
If you want to report specific errors or modify an error before reporting it, you can pass a function to the `sendErrorsInTraces.transform` option, like so:
456+
If you want to report specific errors or modify an error before reporting it, you can pass a function to the `sendErrors.transform` option, like so:
457457

458458
```ts {4-6}
459459
new ApolloServer({
460460
// etc.
461461
plugins: [
462462
ApolloServerPluginUsageReporting({
463-
sendErrorsInTraces: {
463+
sendErrors: {
464464
transform: (err) => {
465465
if (err.extensions.code === 'MY_CUSTOM_CODE') {
466-
// returning null will skip reporting this error
466+
// returning null will skip reporting this error
467467
return null;
468468
}
469469

@@ -483,6 +483,8 @@ The function you pass to `transform` is called for each error (`GraphQLError`) t
483483
- Return a modified form of the error (e.g., by changing the `err.message` to remove potentially sensitive information)
484484
- Return `null` to prevent the error from being reported entirely
485485

486+
Note that returning `null`` also affects Studio's aggregated statistics about how many operations contain errors and at what paths those errors appear.
487+
486488
[As mentioned above](#for-client-responses), you can use the `unwrapResolverError` (from `@apollo/server/errors`) to remove the `GraphQLError` wrapping an original error.
487489

488490
<!-- TODO(AS4) Replace link once plugin docs are updated -->
@@ -502,7 +504,7 @@ const server = new ApolloServer({
502504
plugins: [
503505
// highlight-start
504506
ApolloServerPluginUsageReporting({
505-
sendErrorsInTraces: {
507+
sendErrors: {
506508
transform: (err) => {
507509
// Return `null` to avoid reporting `UNAUTHENTICATED` errors
508510
if (err.extensions.code === 'UNAUTHENTICATED') {
@@ -536,7 +538,7 @@ const server = new ApolloServer({
536538
resolvers,
537539
plugins: [
538540
ApolloServerPluginUsageReporting({
539-
sendErrorsInTraces: {
541+
sendErrors: {
540542
transform: (err) => {
541543
// Using a more stable, known error extension (e.g. `err.code`) would be
542544
// more defensive, however checking the `message` might serve most needs!
@@ -565,7 +567,7 @@ If you _do_ want to send an error's details to Apollo Studio, but need to redact
565567

566568
For example, if there is personally identifiable information in the error `message`, like an API key:
567569

568-
```ts
570+
```ts
569571
import { GraphQLError } from 'graphql';
570572

571573
throw new GraphQLError(
@@ -584,7 +586,7 @@ const server = new ApolloServer({
584586
resolvers,
585587
plugins: [
586588
ApolloServerPluginUsageReporting({
587-
sendErrorsInTraces: {
589+
sendErrors: {
588590
transform: (err) => {
589591
// Make sure that a specific pattern is removed from all error messages.
590592
err.message = err.message.replace(/x-api-key:[A-Z0-9-]+/, 'REDACTED');

docs/source/migration.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ ApolloServerPluginUsageReporting({
936936

937937
In Apollo Server 3, you can specify a function to rewrite errors before sending them to Apollo's server via the `rewriteError` option to `ApolloServerPluginUsageReporting` (for monoliths) and `ApolloServerPluginInlineTrace` (for subgraphs).
938938

939-
In Apollo Server 4, you specify the same function as the `transform` option on the `sendErrorsInTraces` option to `ApolloServerPluginUsageReporting` and the `includeErrors` option to `ApolloServerPluginInlineTrace`.
939+
In Apollo Server 4, you specify the same function as the `transform` option on the `sendErrors` option to `ApolloServerPluginUsageReporting` and the `includeErrors` option to `ApolloServerPluginInlineTrace`.
940940

941941
(Additionally, the [default behavior has changed to mask errors](#usage-reporting-and-inline-trace-plugins-mask-errors-by-default).)
942942

@@ -962,7 +962,7 @@ you can now write:
962962
// monoliths
963963
new ApolloServer({
964964
plugins: [ApolloServerPluginUsageReporting({
965-
sendErrorsInTraces: { transform: rewriteError },
965+
sendErrors: { transform: rewriteError },
966966
})],
967967
// ...
968968
})
@@ -1599,7 +1599,7 @@ To restore the Apollo Server 3 behavior, you can pass `{ unmodified: true }` to
15991599
// monoliths
16001600
new ApolloServer({
16011601
plugins: [ApolloServerPluginUsageReporting({
1602-
sendErrorsInTraces: { unmodified: true },
1602+
sendErrors: { unmodified: true },
16031603
})],
16041604
// ...
16051605
})
@@ -1613,7 +1613,7 @@ new ApolloServer({
16131613
})
16141614
```
16151615

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

16181618
## Renamed packages
16191619

packages/integration-testsuite/src/apolloServerTests.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,14 +1139,14 @@ export function defineIntegrationTestSuiteApolloServerTests(
11391139
});
11401140

11411141
describe('error munging', () => {
1142-
describe('sendErrorsInTraces', () => {
1142+
describe('sendErrors', () => {
11431143
it('new error', async () => {
11441144
throwError.mockImplementationOnce(() => {
11451145
throw new Error('transform nope');
11461146
});
11471147

11481148
await setupApolloServerAndFetchPair({
1149-
sendErrorsInTraces: {
1149+
sendErrors: {
11501150
transform: () =>
11511151
new GraphQLError('rewritten as a new error'),
11521152
},
@@ -1191,7 +1191,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
11911191
});
11921192

11931193
await setupApolloServerAndFetchPair({
1194-
sendErrorsInTraces: {
1194+
sendErrors: {
11951195
transform: (err) => {
11961196
err.message = 'rewritten as a modified error';
11971197
return err;
@@ -1236,7 +1236,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
12361236
});
12371237

12381238
await setupApolloServerAndFetchPair({
1239-
sendErrorsInTraces: { transform: () => null },
1239+
sendErrors: { transform: () => null },
12401240
});
12411241

12421242
const result = await apolloFetch({
@@ -1271,7 +1271,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
12711271
});
12721272

12731273
await setupApolloServerAndFetchPair({
1274-
sendErrorsInTraces: {
1274+
sendErrors: {
12751275
// @ts-expect-error (not allowed to be undefined)
12761276
transform: () => undefined,
12771277
},
@@ -1319,7 +1319,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
13191319
});
13201320

13211321
await setupApolloServerAndFetchPair({
1322-
sendErrorsInTraces: {
1322+
sendErrors: {
13231323
unmodified: true,
13241324
},
13251325
});
@@ -1370,7 +1370,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
13701370
});
13711371

13721372
await setupApolloServerAndFetchPair({
1373-
sendErrorsInTraces: {
1373+
sendErrors: {
13741374
masked: true,
13751375
},
13761376
});

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,24 @@ describe('end-to-end', () => {
174174
).toBeTruthy();
175175
});
176176

177+
it('sendTraces: false', async () => {
178+
const { report } = await runTest({ pluginOptions: { sendTraces: false } });
179+
180+
expect(Object.keys(report.tracesPerQuery)).toHaveLength(1);
181+
expect(Object.keys(report.tracesPerQuery)[0]).toMatch(/^# q\n/);
182+
const tracesAndStats = Object.values(report.tracesPerQuery)[0]!;
183+
expect(tracesAndStats.trace).toHaveLength(0);
184+
expect(tracesAndStats.statsWithContext).toHaveLength(1);
185+
const contextualizedStats = (
186+
tracesAndStats.statsWithContext as ContextualizedStats[]
187+
)[0]!;
188+
expect(contextualizedStats.queryLatencyStats?.requestCount).toBe(1);
189+
expect(
190+
contextualizedStats.perTypeStat['User'].perFieldStat?.['name']
191+
.observedExecutionCount,
192+
).toBe(1);
193+
});
194+
177195
[
178196
{
179197
testName: 'fails parse for non-parsable gql',

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

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,63 @@ export interface ApolloServerPluginUsageReportingOptions<
1414
> {
1515
//#region Configure exactly which data should be sent to Apollo.
1616
/**
17-
* By default, Apollo Server does not send the values of any GraphQL variables to Apollo's servers, because variable
18-
* values often contain the private data of your app's users. If you'd like variable values to be included in traces, set this option.
19-
* This option can take several forms:
17+
* Apollo Server's usage reports describe each individual request in one of
18+
* two ways: as a "trace" (a detailed description of the specific request,
19+
* including a query plan and resolver tree with timings and errors, as well
20+
* as optional details like variable values and HTTP headers), or as part of
21+
* aggregated "stats" (where invocations of the same operation from the same
22+
* client program are aggregated together rather than described individually).
23+
* Apollo Server uses an heuristic to decide which operations to describe as
24+
* traces and which to aggregate as stats.
25+
*
26+
* By setting the `sendTraces` option to `false`, Apollo Server will describe
27+
* *all* operations as stats; individual requests will never be broken out
28+
* into separate traces. If you set `sendTraces: false`, the Traces view in
29+
* Apollo Studio will not show any traces, but all other Studio functionality
30+
* should be unaffected.
31+
*
32+
* Note that the values of `sendVariableValues`, `sendHeaders`, and
33+
* `sendUnexecutableOperationDocuments` are irrelevant if you set
34+
* `sendTraces: false`, because those options control data that is contained
35+
* only in traces and not in stats.
36+
*
37+
* Setting `sendTraces: false` does *NOT* imply `fieldLevelInstrumentation:
38+
* 0`. Apollo Server can still take advantage of field-level instrumentation
39+
* (either directly for monolith servers, or via federated tracing for
40+
* Gateways) in order to accurately report field execution usage in "stats".
41+
* This option only controls whether data is sent to Apollo's servers as
42+
* traces, not whether traces are internally used to learn about usage.
43+
*/
44+
sendTraces?: boolean;
45+
46+
/**
47+
* By default, Apollo Server does not send the values of any GraphQL variables
48+
* to Apollo's servers, because variable values often contain the private data
49+
* of your app's users. If you'd like variable values to be included in
50+
* traces, set this option. This option can take several forms:
2051
* - { none: true }: don't send any variable values (DEFAULT)
2152
* - { all: true}: send all variable values
22-
* - { transform: ... }: a custom function for modifying variable values. Keys added by the custom function will
23-
* be removed, and keys removed will be added back with an empty value. For security reasons, if an error occurs within this function, all variable values will be replaced with `[PREDICATE_FUNCTION_ERROR]`.
24-
* - { exceptNames: ... }: a case-sensitive list of names of variables whose values should not be sent to Apollo servers
25-
* - { onlyNames: ... }: A case-sensitive list of names of variables whose values will be sent to Apollo servers
53+
* - { transform: ... }: a custom function for modifying variable values. Keys
54+
* added by the custom function will be removed, and keys removed will be
55+
* added back with an empty value. For security reasons, if an error occurs
56+
* within this function, all variable values will be replaced with
57+
* `[PREDICATE_FUNCTION_ERROR]`.
58+
* - { exceptNames: ... }: a case-sensitive list of names of variables whose
59+
* values should not be sent to Apollo servers
60+
* - { onlyNames: ... }: A case-sensitive list of names of variables whose
61+
* values will be sent to Apollo servers
62+
*
63+
* Defaults to not sending any variable values if both this parameter and the
64+
* deprecated `privateVariables` are not set. The report will indicate each
65+
* private variable key whose value was redacted by { none: true } or {
66+
* exceptNames: [...] }.
2667
*
27-
* Defaults to not sending any variable values if both this parameter and
28-
* the deprecated `privateVariables` are not set. The report will
29-
* indicate each private variable key whose value was redacted by { none: true } or { exceptNames: [...] }.
68+
* The value of this option is not relevant if you set `sendTraces: false`,
69+
* because variable values only appear in traces.
3070
*/
3171
sendVariableValues?: VariableValueOptions;
3272
/**
33-
* By default, Apollo Server does not send the list of HTTP headers and values
73+
* By default, Apollo Server does not send the HTTP request headers and values
3474
* to Apollo's servers, as these headers may contain your users' private data.
3575
* If you'd like this information included in traces, set this option. This
3676
* option can take several forms:
@@ -44,6 +84,9 @@ export interface ApolloServerPluginUsageReportingOptions<
4484
*
4585
* Unlike with sendVariableValues, names of dropped headers are not reported.
4686
* The headers 'authorization', 'cookie', and 'set-cookie' are never reported.
87+
*
88+
* The value of this option is not relevant if you set `sendTraces: false`,
89+
* because request headers only appear in traces.
4790
*/
4891
sendHeaders?: SendValuesBaseOptions;
4992
/**
@@ -65,10 +108,12 @@ export interface ApolloServerPluginUsageReportingOptions<
65108
* (either a new error, or its potentially-modified argument) or `null`.
66109
* This error is used in the report to Apollo servers; if `null`, the error
67110
* is not included in traces or error statistics.
111+
*
112+
* If you set `sendTraces: false`, then the only relevant aspect of this
113+
* option is whether you return `null` from a `transform` function or not
114+
* (which affects aggregated error statistics).
68115
*/
69-
sendErrorsInTraces?: SendErrorsOptions;
70-
71-
// We should strongly consider changing the default to false in AS4.
116+
sendErrors?: SendErrorsOptions;
72117

73118
/**
74119
* This option allows you to choose if Apollo Server should calculate detailed
@@ -127,8 +172,9 @@ export interface ApolloServerPluginUsageReportingOptions<
127172
* (Note that returning true here does *not* mean that the data derived from
128173
* field-level instrumentation must be transmitted to Apollo Studio's servers
129174
* in the form of a trace; it may still be aggregated locally to statistics.
130-
* But either way this operation will contribute to the "field executions"
131-
* statistic and timing hints.)
175+
* Similarly, setting `sendTraces: false` does not affect
176+
* `fieldLevelInstrumentation`. But either way this operation will contribute
177+
* to the "field executions" statistic and timing hints.)
132178
*
133179
* The default `fieldLevelInstrumentation` is a function that always returns
134180
* true.
@@ -211,6 +257,9 @@ export interface ApolloServerPluginUsageReportingOptions<
211257
* and the operation name and signature will always be reported with a constant
212258
* identifier. Whether the operation was a parse failure or a validation
213259
* failure will be embedded within the stats report key itself.
260+
*
261+
* The value of this option is not relevant if you set `sendTraces: false`,
262+
* because unexecutable operation documents only appear in traces.
214263
*/
215264
sendUnexecutableOperationDocuments?: boolean;
216265

@@ -225,6 +274,9 @@ export interface ApolloServerPluginUsageReportingOptions<
225274
* Apollo's servers perform their own sampling on received traces; not all
226275
* traces sent to Apollo's servers can be later retrieved via the trace UI.)
227276
*
277+
* If you just want to send all operations as stats, set `sendTraces: false`
278+
* instead of using this experimental hook.
279+
*
228280
* This option is highly experimental and may change or be removed in future
229281
* versions.
230282
*/
@@ -298,7 +350,7 @@ export interface ApolloServerPluginUsageReportingOptions<
298350
/**
299351
* If set, prints all reports as JSON when they are sent. (Note that for
300352
* technical reasons, traces embedded in a report are printed separately when
301-
* they are added to a report.)
353+
* they are added to a report.) Reports are sent through `logger.info`.
302354
*/
303355
debugPrintReports?: boolean;
304356
/**
@@ -307,12 +359,6 @@ export interface ApolloServerPluginUsageReportingOptions<
307359
* about how the signature relates to the operation you executed.
308360
*/
309361
calculateSignature?: (ast: DocumentNode, operationName: string) => string;
310-
/**
311-
* This option includes extra data in reports that helps Apollo validate the
312-
* stats generation code in this plugin. Do not set it; the only impact on
313-
* your app will be a decrease in performance.
314-
*/
315-
internal_includeTracesContributingToStats?: boolean;
316362
//#endregion
317363
}
318364

0 commit comments

Comments
 (0)