-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathgraphql.ts
109 lines (91 loc) · 3.9 KB
/
graphql.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
import { defineIntegration, getRootSpan, spanToJSON } from '@sentry/core';
import { SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION } from '@sentry/opentelemetry';
import type { IntegrationFn } from '@sentry/types';
import { generateInstrumentOnce } from '../../otel/instrument';
import { addOriginToSpan } from '../../utils/addOriginToSpan';
interface GraphqlOptions {
/**
* Do not create spans for resolvers.
*
* Defaults to true.
*/
ignoreResolveSpans?: boolean;
/**
* Don't create spans for the execution of the default resolver on object properties.
*
* When a resolver function is not defined on the schema for a field, graphql will
* use the default resolver which just looks for a property with that name on the object.
* If the property is not a function, it's not very interesting to trace.
* This option can reduce noise and number of spans created.
*
* Defaults to true.
*/
ignoreTrivialResolveSpans?: boolean;
/**
* If this is enabled, a http.server root span containing this span will automatically be renamed to include the operation name.
* Set this to `false` if you do not want this behavior, and want to keep the default http.server span name.
*
* Defaults to true.
*/
useOperationNameForRootSpan?: boolean;
}
const INTEGRATION_NAME = 'Graphql';
export const instrumentGraphql = generateInstrumentOnce<GraphqlOptions>(
INTEGRATION_NAME,
(_options: GraphqlOptions = {}) => {
const options = getOptionsWithDefaults(_options);
return new GraphQLInstrumentation({
...options,
responseHook(span) {
addOriginToSpan(span, 'auto.graphql.otel.graphql');
const attributes = spanToJSON(span).data || {};
// If operation.name is not set, we fall back to use operation.type only
const operationType = attributes['graphql.operation.type'];
const operationName = attributes['graphql.operation.name'];
if (options.useOperationNameForRootSpan && operationType) {
const rootSpan = getRootSpan(span);
// We guard to only do this on http.server spans
const rootSpanAttributes = spanToJSON(rootSpan).data || {};
const existingOperations = rootSpanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION] || [];
const newOperation = operationName ? `${operationType} ${operationName}` : `${operationType}`;
// We keep track of each operation on the root span
// This can either be a string, or an array of strings (if there are multiple operations)
if (Array.isArray(existingOperations)) {
existingOperations.push(newOperation);
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION, existingOperations);
} else if (existingOperations) {
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION, [existingOperations, newOperation]);
} else {
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION, newOperation);
}
}
},
});
},
);
const _graphqlIntegration = ((options: GraphqlOptions = {}) => {
return {
name: INTEGRATION_NAME,
setupOnce() {
// We set defaults here, too, because otherwise we'd update the instrumentation config
// to the config without defaults, as `generateInstrumentOnce` automatically calls `setConfig(options)`
// when being called the second time
instrumentGraphql(getOptionsWithDefaults(options));
},
};
}) satisfies IntegrationFn;
/**
* GraphQL integration
*
* Capture tracing data for GraphQL.
*/
export const graphqlIntegration = defineIntegration(_graphqlIntegration);
function getOptionsWithDefaults(options?: GraphqlOptions): GraphqlOptions {
return {
ignoreResolveSpans: true,
ignoreTrivialResolveSpans: true,
useOperationNameForRootSpan: true,
...options,
};
}