@subsquid/apollo-server-core
Version:
Core engine for Apollo GraphQL server
141 lines (124 loc) • 5.5 kB
text/typescript
import { Trace } from 'apollo-reporting-protobuf';
import { TraceTreeBuilder } from '../traceTreeBuilder';
import type { ApolloServerPluginUsageReportingOptions } from '../usageReporting/options';
import type { InternalApolloServerPlugin } from '../../internalPlugin';
import { schemaIsFederated } from '../schemaIsFederated';
export interface ApolloServerPluginInlineTraceOptions {
/**
* By default, all errors from this service get included in the trace. You
* can specify a filter function to exclude specific errors from being
* reported by returning an explicit `null`, or you can mask certain details
* of the error by modifying it and returning the modified error.
*/
rewriteError?: ApolloServerPluginUsageReportingOptions<never>['rewriteError'];
/**
* This option is for internal use by `apollo-server-core` only.
*
* By default we want to enable this plugin for federated 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
* ApolloServerPluginInlineTraceDisabled themselves), `apollo-server-core`
* always installs this plugin and uses this option to make sure traces are
* only included if the schema appears to be federated.
*/
__onlyIfSchemaIsFederated?: boolean;
}
// This ftv1 plugin produces a base64'd Trace protobuf containing only the
// durationNs, startTime, endTime, and root fields. This output is placed
// on the `extensions`.`ftv1` property of the response. The Apollo Gateway
// utilizes this data to construct the full trace and submit it to Apollo's
// usage reporting ingress.
export function ApolloServerPluginInlineTrace(
options: ApolloServerPluginInlineTraceOptions = Object.create(null),
): InternalApolloServerPlugin {
let enabled: boolean | null = options.__onlyIfSchemaIsFederated ? null : true;
return {
__internal_plugin_id__() {
return 'InlineTrace';
},
async serverWillStart({ schema, logger }) {
// Handle the case that the plugin was implicitly installed. We only want it
// to actually be active if the schema appears to be federated. If you don't
// like the log line, just install `ApolloServerPluginInlineTrace()` in
// `plugins` yourself.
if (enabled === null) {
enabled = schemaIsFederated(schema);
if (enabled) {
logger.info(
'Enabling inline tracing for this federated service. To disable, use ' +
'ApolloServerPluginInlineTraceDisabled.',
);
}
}
},
async requestDidStart({ request: { http }, metrics }) {
if (!enabled) {
return;
}
const treeBuilder = new TraceTreeBuilder({
rewriteError: options.rewriteError,
});
// XXX Provide a mechanism to customize this logic.
if (http?.headers.get('apollo-federation-include-trace') !== 'ftv1') {
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 {
async executionDidStart() {
return {
willResolveField({ info }) {
return treeBuilder.willResolveField(info);
},
};
},
async didEncounterErrors({ errors }) {
treeBuilder.didEncounterErrors(errors);
},
async willSendResponse({ response }) {
// We record the end time at the latest possible time: right before serializing the trace.
// 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,
encodedUint8Array.byteOffset,
encodedUint8Array.byteLength,
);
const extensions =
response.extensions || (response.extensions = Object.create(null));
// This should only happen if another plugin is using the same name-
// space within the `extensions` object and got to it before us.
if (typeof extensions.ftv1 !== 'undefined') {
throw new Error('The `ftv1` extension was already present.');
}
extensions.ftv1 = encodedBuffer.toString('base64');
},
};
},
};
}
// This plugin does nothing, but it ensures that ApolloServer won't try
// to add a default ApolloServerPluginInlineTrace.
export function ApolloServerPluginInlineTraceDisabled(): InternalApolloServerPlugin {
return {
__internal_plugin_id__() {
return 'InlineTrace';
},
};
}