Skip to content

Commit 1a9ce86

Browse files
committed
ref(server-utils): Streamline graphql integration
1 parent efb1232 commit 1a9ce86

13 files changed

Lines changed: 55 additions & 99 deletions

File tree

packages/deno/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export {
126126
firebaseIntegration,
127127
genericPoolIntegration,
128128
googleGenAIIntegration,
129-
graphqlDiagnosticsIntegration,
129+
graphqlIntegration,
130130
hapiIntegration,
131131
kafkajsIntegration,
132132
knexIntegration,

packages/deno/src/sdk.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
firebaseIntegration,
2020
genericPoolIntegration,
2121
googleGenAIIntegration,
22-
graphqlDiagnosticsIntegration,
22+
graphqlIntegration,
2323
hapiIntegration,
2424
kafkajsIntegration,
2525
koaIntegration,
@@ -65,7 +65,7 @@ export function getDefaultIntegrations(_options: Options): Integration[] {
6565
denoServeIntegration(),
6666
denoHttpIntegration(),
6767
denoRedisIntegration(),
68-
graphqlDiagnosticsIntegration(),
68+
graphqlIntegration(),
6969
vercelAiIntegration(),
7070
// orchestrion-based instrumentations. We add a deliberate list here rather
7171
// than every channel integration: each one needs a Deno test proving it

packages/node/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export {
1111
firebaseIntegration,
1212
genericPoolIntegration,
1313
googleGenAIIntegration,
14-
graphqlDiagnosticsIntegration as graphqlIntegration,
14+
graphqlIntegration,
1515
hapiIntegration,
1616
kafkajsIntegration as kafkaIntegration,
1717
knexIntegration,

packages/node/src/integrations/tracing/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
firebaseIntegration,
88
genericPoolIntegration,
99
googleGenAIIntegration,
10-
graphqlDiagnosticsIntegration,
10+
graphqlIntegration,
1111
hapiIntegration,
1212
kafkajsIntegration,
1313
koaIntegration,
@@ -31,7 +31,7 @@ export function getAutoPerformanceIntegrations(): Integration[] {
3131
return [
3232
expressIntegration(),
3333
fastifyIntegration(),
34-
graphqlDiagnosticsIntegration(),
34+
graphqlIntegration(),
3535
mongodbIntegration(),
3636
mongooseIntegration(),
3737
mysqlIntegration(),

packages/server-utils/src/graphql/index.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

packages/server-utils/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
export * from './exports';
22

33
// Exports using diagnostics channels
4-
export { graphqlIntegration } from './graphql';
54
export { mongooseIntegration } from './mongoose';
65
export { mysql2Integration } from './mysql2';
76
export { instrumentPrisma, prismaIntegration } from './prisma';

packages/server-utils/src/graphql/graphql-dc-subscriber.ts renamed to packages/server-utils/src/integrations/graphql/graphql-dc-subscriber.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
SPAN_STATUS_ERROR,
88
startInactiveSpan,
99
} from '@sentry/core';
10-
import { bindTracingChannelToSpan } from '../tracing-channel';
11-
import type { GraphqlDocumentNode } from './utils';
10+
import { bindTracingChannelToSpan } from '../../tracing-channel';
11+
import type { GraphqlDocumentNode } from './types';
1212
import { collectGraphqlDocument, getOperationSpanName, hasResultErrors, renameRootSpanWithOperation } from './utils';
1313

1414
// Channel names published by graphql >= 17.0.0 (see graphql-js `src/diagnostics.ts`).
@@ -85,7 +85,7 @@ export interface GraphqlResolveData {
8585
}
8686

8787
/** Options controlling which graphql channels the subscriber emits spans for. */
88-
export interface GraphqlDiagnosticChannelsOptions {
88+
export interface GraphQLOptions {
8989
/**
9090
* Do not create spans for resolvers. Resolver spans are per-field and can be very high volume.
9191
* Defaults to `true`.
@@ -128,7 +128,7 @@ export type GraphqlTracingChannelFactory = <T extends object>(name: string) => T
128128
*/
129129
export function subscribeGraphqlDiagnosticChannels(
130130
tracingChannel: GraphqlTracingChannelFactory,
131-
options: GraphqlDiagnosticChannelsOptions = {},
131+
options: GraphQLOptions = {},
132132
): void {
133133
const ignoreResolveSpans = options.ignoreResolveSpans !== false;
134134
const ignoreTrivialResolveSpans = options.ignoreTrivialResolveSpans !== false;

packages/server-utils/src/integrations/graphql/index.ts

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as diagnosticsChannel from 'node:diagnostics_channel';
2-
import type { Client, IntegrationFn } from '@sentry/core';
3-
import { defineIntegration, extendIntegration } from '@sentry/core';
4-
import { graphqlIntegration as graphqlNativeIntegration } from '../../graphql';
5-
import type { GraphqlDiagnosticChannelsOptions } from '../../graphql/graphql-dc-subscriber';
2+
import type { IntegrationFn } from '@sentry/core';
3+
import { defineIntegration, waitForTracingChannelBinding } from '@sentry/core';
4+
import { subscribeGraphqlDiagnosticChannels, type GraphQLOptions } from './graphql-dc-subscriber';
65
import { CHANNELS } from '../../orchestrion/channels';
76
import { graphqlModuleNames } from '../../orchestrion/config/graphql';
87
import { invokeOrchestrionInstrumentation } from '../../orchestrion/instrumentation';
@@ -28,15 +27,15 @@ interface GraphqlChannelContext {
2827
error?: unknown;
2928
}
3029

31-
function getOptionsWithDefaults(options: GraphqlDiagnosticChannelsOptions): GraphqlResolvedConfig {
30+
function getOptionsWithDefaults(options: GraphQLOptions): GraphqlResolvedConfig {
3231
return {
3332
ignoreResolveSpans: options.ignoreResolveSpans !== false,
3433
ignoreTrivialResolveSpans: options.ignoreTrivialResolveSpans !== false,
3534
useOperationNameForRootSpan: options.useOperationNameForRootSpan !== false,
3635
};
3736
}
3837

39-
const _graphqlIntegration = ((options: GraphqlDiagnosticChannelsOptions = {}) => {
38+
const _graphqlIntegration = ((options: GraphQLOptions = {}) => {
4039
const config = getOptionsWithDefaults(options);
4140
const getConfig = (): GraphqlResolvedConfig => config;
4241

@@ -45,6 +44,9 @@ const _graphqlIntegration = ((options: GraphqlDiagnosticChannelsOptions = {}) =>
4544
setup(client) {
4645
invokeOrchestrionInstrumentation(client, graphqlModuleNames, instrumentGraphql, [config, getConfig]);
4746
},
47+
setupOnce() {
48+
setupNativeGraphQLInstrumentation(options);
49+
},
4850
};
4951
}) satisfies IntegrationFn;
5052

@@ -66,30 +68,20 @@ function instrumentGraphql(config: GraphqlResolvedConfig, getConfig: () => Graph
6668
);
6769
}
6870

69-
/**
70-
* Orchestrion-driven graphql integration for graphql v14–16 (v17 publishes native
71-
* `diagnostics_channel` events handled by `@sentry/server-utils`'s graphql integration instead).
72-
*
73-
* Subscribes to the `orchestrion:graphql:{parse,validate,execute}` channels the orchestrion code
74-
* transform injects into `graphql`'s `language/parser.js`, `validation/validate.js` and
75-
* `execution/execute.js`, emitting spans identical to the native path. Requires the orchestrion
76-
* runtime hook or bundler plugin.
77-
*/
78-
export const graphqlIntegration = defineIntegration(_graphqlIntegration);
71+
function setupNativeGraphQLInstrumentation(options: GraphQLOptions) {
72+
if (!diagnosticsChannel.tracingChannel) {
73+
return;
74+
}
75+
76+
// Subscribe to graphql's native tracing channels (graphql >= 17).
77+
// This is a no-op on versions that don't publish to the channels, so it is always safe to call.
78+
waitForTracingChannelBinding(() => {
79+
subscribeGraphqlDiagnosticChannels(diagnosticsChannel.tracingChannel, options);
80+
});
81+
}
7982

8083
/**
81-
* The complete graphql diagnostics-channel integration: the native subscriber (graphql v17) composed
82-
* with the orchestrion subscriber (v14–16), so opting into injection instruments every supported
83-
* version via diagnostics channels without the OTel patcher. Reuses the OTel `Graphql` name so
84-
* enabling injection swaps this in for it.
84+
* Instrument the graphql library.
85+
* This works for graphql v14-v17.
8586
*/
86-
export const graphqlDiagnosticsIntegration = (options?: GraphqlDiagnosticChannelsOptions) => {
87-
const orchestrion = graphqlIntegration(options);
88-
// The native half is the base integration's own `setupOnce`; the orchestrion half
89-
// registers lazily via `setup` (only once `graphql` is injected), so it isn't
90-
// merged onto the base `setupOnce` — both run.
91-
return extendIntegration(graphqlNativeIntegration(options), {
92-
name: INTEGRATION_NAME,
93-
setup: (client: Client) => orchestrion.setup?.(client),
94-
});
95-
};
87+
export const graphqlIntegration = defineIntegration(_graphqlIntegration);

packages/server-utils/src/integrations/graphql/spans.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,8 @@ import {
1414
SPAN_STATUS_ERROR,
1515
startInactiveSpan,
1616
} from '@sentry/core';
17-
import type { GraphqlDocumentNode } from '../../graphql/utils';
18-
import {
19-
collectGraphqlDocument,
20-
getOperationSpanName,
21-
hasResultErrors,
22-
renameRootSpanWithOperation,
23-
} from '../../graphql/utils';
17+
import type { GraphqlDocumentNode } from './types';
18+
import { collectGraphqlDocument, getOperationSpanName, hasResultErrors, renameRootSpanWithOperation } from './utils';
2419
import { GRAPHQL_DATA_SYMBOL, ORIGIN, SPAN_NAME_EXECUTE, SPAN_NAME_PARSE, SPAN_NAME_VALIDATE } from './constants';
2520
import { getOperation, wrapFields, wrapFieldResolver } from './resolvers';
2621
import type {

packages/server-utils/src/integrations/graphql/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,19 @@ export interface GraphqlResolvedConfig {
3131
ignoreTrivialResolveSpans: boolean;
3232
useOperationNameForRootSpan: boolean;
3333
}
34+
35+
/** Minimal shape of a graphql-js lexer token, enough to locate literal spans for redaction. */
36+
export interface GraphqlToken {
37+
kind: string;
38+
start: number;
39+
end: number;
40+
next?: GraphqlToken | null;
41+
}
42+
43+
/** Minimal shape of a parsed graphql-js `DocumentNode`, enough to read its source and tokens. */
44+
export interface GraphqlDocumentNode {
45+
loc?: {
46+
startToken?: GraphqlToken;
47+
source?: { body?: string };
48+
};
49+
}

0 commit comments

Comments
 (0)