@apollo/server
Version:
Core engine for Apollo GraphQL server
415 lines (402 loc) • 19.8 kB
text/typescript
import type { GraphQLError, DocumentNode } from 'graphql';
import type {
GraphQLRequestContextDidResolveOperation,
GraphQLRequestContext,
GraphQLRequestContextWillSendResponse,
BaseContext,
} from '../../externalTypes/index.js';
import type { Logger } from '@apollo/utils.logger';
import type { Trace } from '@apollo/usage-reporting-protobuf';
import type { Fetcher } from '@apollo/utils.fetcher';
export interface ApolloServerPluginUsageReportingOptions<
TContext extends BaseContext,
> {
//#region Configure exactly which data should be sent to Apollo.
/**
* Apollo Server's usage reports describe each individual request in one of
* two ways: as a "trace" (a detailed description of the specific request,
* including a query plan and resolver tree with timings and errors, as well
* as optional details like variable values and HTTP headers), or as part of
* aggregated "stats" (where invocations of the same operation from the same
* client program are aggregated together rather than described individually).
* Apollo Server uses an heuristic to decide which operations to describe as
* traces and which to aggregate as stats.
*
* By setting the `sendTraces` option to `false`, Apollo Server will describe
* *all* operations as stats; individual requests will never be broken out
* into separate traces. If you set `sendTraces: false`, then Apollo Studio's
* Traces view won't show any traces (other Studio functionality will be
* unaffected).
*
* Note that the values of `sendVariableValues`, `sendHeaders`, and
* `sendUnexecutableOperationDocuments` are irrelevant if you set
* `sendTraces: false`, because those options control data that is contained
* only in traces (not in stats).
*
* Setting `sendTraces: false` does *NOT* imply `fieldLevelInstrumentation:
* 0`. Apollo Server can still take advantage of field-level instrumentation
* (either directly for monolith servers, or via federated tracing for
* Gateways) in order to accurately report field execution usage in "stats".
* This option only controls whether data is sent to Apollo's servers as
* traces, not whether traces are internally used to learn about usage.
*/
sendTraces?: boolean;
/**
* By default, Apollo Server does not send the values of any GraphQL variables
* to Apollo's servers, because variable 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. This option can take several forms:
* - { none: true }: don't send any variable values (DEFAULT)
* - { all: true}: send all variable values
* - { transform: ... }: a custom function for modifying variable values. The
* function receives `variables` and `operationString` and should return a
* record of `variables` with the same keys as the `variables` it receives
* (added variables will be ignored and removed variables will be reported
* with an empty value). For security reasons, if an error occurs within
* this function, all variable values will be replaced with
* `[PREDICATE_FUNCTION_ERROR]`.
* - { exceptNames: ... }: a case-sensitive list of names of variables whose
* values should not be sent to Apollo servers
* - { onlyNames: ... }: A case-sensitive list of names of variables whose
* values will be sent to Apollo servers
*
* Defaults to not sending any variable values if both this parameter and the
* deprecated `privateVariables` are not set. The report will indicate each
* private variable key whose value was redacted by { none: true } or {
* exceptNames: [...] }.
*
* The value of this option is not relevant if you set `sendTraces: false`,
* because variable values only appear in traces.
*/
sendVariableValues?: VariableValueOptions;
/**
* By default, Apollo Server does not send the HTTP request headers and values
* to Apollo's servers, as these headers may contain your users' private data.
* If you'd like this information included in traces, set this option. This
* option can take several forms:
*
* - { none: true } to drop all HTTP request headers (DEFAULT)
* - { all: true } to send the values of all HTTP request headers
* - { exceptNames: Array<String> } A case-insensitive list of names of HTTP
* headers whose values should not be sent to Apollo servers
* - { onlyNames: Array<String> }: A case-insensitive list of names of HTTP
* headers whose values will be sent to Apollo servers
*
* Unlike with sendVariableValues, names of dropped headers are not reported.
* The headers 'authorization', 'cookie', and 'set-cookie' are never reported.
*
* The value of this option is not relevant if you set `sendTraces: false`,
* because request headers only appear in traces.
*/
sendHeaders?: SendValuesBaseOptions;
/**
* By default, if a trace contains errors, the errors are reported to Apollo
* servers with the message `<masked>`. The errors are associated with
* specific paths in the operation, but do not include the original error
* message or any extensions such as the error `code`, as those details may
* contain your users' private data. The extension `maskedBy:
* 'ApolloServerPluginUsageReporting'` is added.
*
* If you'd like details about the error included in traces, set this option.
* This option can take several forms:
*
* - { masked: true }: mask error messages and omit extensions (DEFAULT)
* - { unmodified: true }: send all error messages and extensions to Apollo
* servers
* - { transform: ... }: a custom function for transforming errors. This
* function receives a `GraphQLError` and may return a `GraphQLError`
* (either a new error, or its potentially-modified argument) or `null`.
* This error is used in the report to Apollo servers; if `null`, the error
* is not included in traces or error statistics.
*
* If you set `sendTraces: false`, then the only relevant aspect of this
* option is whether you return `null` from a `transform` function or not
* (which affects aggregated error statistics).
*/
sendErrors?: SendErrorsOptions;
/**
* This option allows you to choose if Apollo Server should calculate detailed
* per-field statistics for a particular request. It is only called for
* executable operations: operations which parse and validate properly and
* which do not have an unknown operation name. It is not called if an
* `includeRequest` hook is provided and returns false.
*
* You can either pass an async function or a number. The function receives a
* `GraphQLRequestContext`. (The effect of passing a number is described
* later.) Your function can return a boolean or a number; returning false is
* equivalent to returning 0 and returning true is equivalent to returning 1.
*
* Returning false (or 0) means that Apollo Server will only pay attention to
* overall properties of the operation, like what GraphQL operation is
* executing and how long the entire operation takes to execute, and not
* anything about field-by-field execution.
*
* If you return false (or 0), this operation *will* still contribute to most
* features of Studio, such as schema checks, the Operations page, and the
* "referencing operations" statistic on the Fields page, etc.
*
* If you return false (or 0), this operation will *not* contribute to the
* "field executions" statistic on the Fields page or to the execution timing
* hints optionally displayed in Studio Explorer or in vscode-graphql.
* Additionally, this operation will not produce a trace that can be viewed on
* the Traces section of the Operations page.
*
* Returning false (or 0) for some or all operations can improve your server's
* performance, as the overhead of calculating complete traces is not always
* negligible. This is especially the case if this server is an Apollo
* Gateway, as captured traces are transmitted from the subgraph to the
* Gateway in-band inside the actual GraphQL response.
*
* Returning a positive number means that Apollo Server will track each field
* execution and send Apollo Studio statistics on how many times each field
* was executed and what the per-field performance was. Apollo Server sends
* both a precise observed execution count and an estimated execution count.
* The former is calculated by counting each field execution as 1, and the
* latter is calculated by counting each field execution as the number
* returned from this hook, which can be thought of as a weight.
*
* Passing a number `x` (which should be between 0 and 1 inclusive) for
* `fieldLevelInstrumentation` is equivalent to passing the function `async ()
* => Math.random() < x ? 1/x : 0`. For example, if you pass 0.01, then 99%
* of the time this function will return 0, and 1% of the time this function
* will return 100. So 99% of the time Apollo Server will not track field
* executions, and 1% of the time Apollo Server will track field executions
* and send them to Apollo Studio both as an exact observed count and as an
* "estimated" count which is 100 times higher. Generally, the weights you
* return should be roughly the reciprocal of the probability that the
* function returns non-zero; however, you're welcome to craft a more
* sophisticated function, such as one that uses a higher probability for
* rarer operations and a lower probability for more common operations.
*
* (Note that returning true here does *not* mean that the data derived from
* field-level instrumentation must be transmitted to Apollo Studio's servers
* in the form of a trace; it may still be aggregated locally to statistics.
* Similarly, setting `sendTraces: false` does not affect
* `fieldLevelInstrumentation`. But either way this operation will contribute
* to the "field executions" statistic and timing hints.)
*
* The default `fieldLevelInstrumentation` is a function that always returns
* true.
*/
fieldLevelInstrumentation?:
| number
| ((
request: GraphQLRequestContextDidResolveOperation<TContext>,
) => Promise<number | boolean>);
/**
* This option allows you to choose if a particular request should be
* represented in the usage reporting sent to Apollo servers. By default, all
* requests are included. If this async predicate function is specified, its
* return value will determine whether a given request is included.
*
* Note that returning false here means that the operation will be completely
* ignored by all Apollo Studio features. If you merely want to improve
* performance by skipping the field-level execution trace, set the
* `fieldLevelInstrumentation` option instead of this one.
*
* The predicate function receives the request context. If validation and
* parsing of the request succeeds, the function will receive the request
* context in the
* [`GraphQLRequestContextDidResolveOperation`](https://www.apollographql.com/docs/apollo-server/integrations/plugins/#didresolveoperation)
* phase, which permits tracing based on dynamic properties, e.g., HTTP
* headers or the `operationName` (when available). Otherwise it will receive
* the request context in the
* [`GraphQLRequestContextWillSendResponse`](https://www.apollographql.com/docs/apollo-server/integrations/plugins/#willsendresponse)
* phase:
*
* (If you don't want any usage reporting at all, don't use this option:
* instead, either avoid specifying an Apollo API key, or use
* ApolloServerPluginUsageReportingDisabled to prevent this plugin from being
* created by default.)
*
* **Example:**
*
* ```js
* includeRequest(requestContext) {
* // Always include `query HomeQuery { ... }`.
* if (requestContext.operationName === "HomeQuery") return true;
*
* // Omit if the "report-to-apollo" header is set to "false".
* if (requestContext.request.http?.headers?.get("report-to-apollo") === "false") {
* return false;
* }
*
* // Otherwise include.
* return true;
* },
* ```
*
*/
includeRequest?: (
request:
| GraphQLRequestContextDidResolveOperation<TContext>
| GraphQLRequestContextWillSendResponse<TContext>,
) => Promise<boolean>;
/**
* By default, this plugin associates client information such as name
* and version with user requests based on HTTP headers starting with
* `apollographql-client-`. If you have another way of communicating
* client information to your server, tell the plugin how it works
* with this option.
*/
generateClientInfo?: GenerateClientInfo<TContext>;
/**
* If you are using the `overrideReportedSchema` option to the schema
* reporting plugin (`ApolloServerPluginSchemaReporting`), you should
* pass the same value here as well, so that the schema ID associated
* with requests in this plugin's usage reports matches the schema
* ID that the other plugin reports.
*/
overrideReportedSchema?: string;
/**
* Whether to include the entire document in the trace if the operation
* was a GraphQL parse or validation error (i.e. failed the GraphQL parse or
* validation phases). This will be included as a separate field on the trace
* and the operation name and signature will always be reported with a constant
* identifier. Whether the operation was a parse failure or a validation
* failure will be embedded within the stats report key itself.
*
* The value of this option is not relevant if you set `sendTraces: false`,
* because unexecutable operation documents only appear in traces.
*/
sendUnexecutableOperationDocuments?: boolean;
/**
* This plugin sends information about operations to Apollo's servers in two
* forms: as detailed operation traces of single operations and as summarized
* statistics of many operations. Each individual operation is described in
* exactly one of those ways. This hook lets you select which operations are
* sent as traces and which are sent as statistics. The default is a heuristic
* that tries to send one trace for each rough duration bucket for each
* operation each minute, plus more if the operations have errors. (Note that
* Apollo's servers perform their own sampling on received traces; not all
* traces sent to Apollo's servers can be later retrieved via the trace UI.)
*
* If you just want to send all operations as stats, set `sendTraces: false`
* instead of using this experimental hook.
*
* This option is highly experimental and may change or be removed in future
* versions.
*/
experimental_sendOperationAsTrace?: (
trace: Trace,
statsReportKey: string,
) => boolean;
//#endregion
//#region Configure the mechanics of communicating with Apollo's servers.
/**
* Sends a usage report after every request. This options is useful for
* stateless environments like Amazon Lambda where processes handle only a
* small number of requests before terminating. It defaults to true when the
* ApolloServer was started in the background with
* `startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests`
* (generally used with serverless frameworks), or false otherwise. (Note that
* "immediately" does not mean synchronously with completing the response, but
* "very soon", such as after a setImmediate call.)
*/
sendReportsImmediately?: boolean;
/**
* Specifies which Fetch API implementation to use when sending usage reports.
*/
fetcher?: Fetcher;
/**
* How often to send reports to Apollo. We'll also send reports when the
* report gets big; see maxUncompressedReportSize.
*/
reportIntervalMs?: number;
/**
* We send a report when the report size will become bigger than this size in
* bytes (default: 4MB). (This is a rough limit --- we ignore the size of the
* report header and some other top level bytes. We just add up the lengths of
* the serialized traces and signatures.)
*/
maxUncompressedReportSize?: number;
/**
* Reporting is retried with exponential backoff up to this many times
* (including the original request). Defaults to 5.
*/
maxAttempts?: number;
/**
* Minimum back-off for retries. Defaults to 100ms.
*/
minimumRetryDelayMs?: number;
/**
* Timeout for each individual attempt to send a report to Apollo. (This is
* for each HTTP POST, not for all potential retries.) Defaults to 30 seconds
* (30000ms).
*/
requestTimeoutMs?: number;
/**
* A logger interface to be used for output and errors. When not provided
* it will default to the server's own `logger` implementation and use
* `console` when that is not available.
*/
logger?: Logger;
/**
* By default, if an error occurs when sending trace reports to Apollo
* servers, its message will be sent to the `error` method on the logger
* specified with the `logger` option to this plugin or to ApolloServer (or to
* `console.error` by default). Specify this function to process errors in a
* different way. (The difference between using this option and using a logger
* is that this option receives the actual Error object whereas `logger.error`
* only receives its message.)
*/
reportErrorFunction?: (err: Error) => void;
//#endregion
//#region Internal and non-recommended options
/**
* The URL base that we send reports to (not including the path). This option
* only needs to be set for testing and Apollo-internal uses.
*/
endpointUrl?: string;
/**
* If set, prints all reports as JSON when they are sent. (Note that for
* technical reasons, traces embedded in a report are printed separately when
* they are added to a report.) Reports are sent through `logger.info`.
*/
debugPrintReports?: boolean;
/**
* Specify the function for creating a signature for a query. See signature.ts
* for details. This option is not recommended, as Apollo's servers make assumptions
* about how the signature relates to the operation you executed.
*/
calculateSignature?: (ast: DocumentNode, operationName: string) => string;
/**
* This option is for internal use by `@apollo/server` only.
*
* By default we want to enable this plugin for non-subgraph schemas only, but
* we need to come up with our list of plugins before we have necessarily
* loaded the schema. So (unless the user installs this plugin or
* ApolloServerPluginUsageReportingDisabled themselves), `@apollo/server`
* always installs this plugin (if API key and graph ref are provided) and
* uses this option to disable usage reporting if the schema is a subgraph.
*/
__onlyIfSchemaIsNotSubgraph?: boolean;
//#endregion
}
export type SendValuesBaseOptions =
| { onlyNames: Array<string> }
| { exceptNames: Array<string> }
| { all: true }
| { none: true };
type VariableValueTransformOptions = {
variables: Record<string, any>;
operationString?: string;
};
export type VariableValueOptions =
| {
transform: (
options: VariableValueTransformOptions,
) => Record<string, any>;
}
| SendValuesBaseOptions;
export type SendErrorsOptions =
| { unmodified: true }
| { masked: true }
| { transform: (err: GraphQLError) => GraphQLError | null };
export interface ClientInfo {
clientName?: string;
clientVersion?: string;
}
export type GenerateClientInfo<TContext extends BaseContext> = (
requestContext: GraphQLRequestContext<TContext>,
) => ClientInfo;