Skip to content

Commit 39070be

Browse files
authored
Merge branch 'develop' into fix/cloudflare-double-instrumentation-flake
2 parents e519752 + a563b18 commit 39070be

19 files changed

Lines changed: 1549 additions & 15 deletions

File tree

.oxlintrc.base.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@
156156
"**/integrations/tracing/tedious/vendored/**/*.ts",
157157
"**/integrations/tracing/hapi/vendored/**/*.ts",
158158
"**/integrations/tracing/mongoose/vendored/**/*.ts",
159-
"**/integrations/tracing/amqplib/vendored/**/*.ts"
159+
"**/integrations/tracing/amqplib/vendored/**/*.ts",
160+
"**/integrations/tracing/graphql/vendored/**/*.ts"
160161
],
161162
"rules": {
162163
"typescript/no-explicit-any": "off"

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7-
Work in this release was contributed by @abcang, @ahmadio, and @victorgarciaesgi. Thank you for your contributions!
7+
Work in this release was contributed by @abcang, @ahmadio, @victorgarciaesgi, and @delorge. Thank you for your contributions!
88

99
- **feat(core): Support array attributes for spans, logs, and metrics ([#20427](https://github.com/getsentry/sentry-javascript/pull/20427))**
1010

packages/cloudflare/src/instrumentations/worker/instrumentFetch.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export function instrumentExportedHandlerFetch<T extends ExportedHandler<any, an
2525
new Proxy(original, {
2626
apply(target, thisArg, args: Parameters<NonNullable<T['fetch']>>) {
2727
const [request, env, ctx] = args;
28+
29+
if (request.method === 'OPTIONS' || request.method === 'HEAD') {
30+
return target.apply(thisArg, args);
31+
}
32+
2833
const context = instrumentContext(ctx);
2934
const options = getFinalOptions(optionsCallback(env), env);
3035
args[1] = instrumentEnv(env, options);
@@ -53,6 +58,10 @@ export function instrumentWorkerEntrypointFetch<T extends WorkerEntrypoint>(
5358
apply(target, thisArg, args: [Request]) {
5459
const [request] = args;
5560

61+
if (request.method === 'OPTIONS' || request.method === 'HEAD') {
62+
return Reflect.apply(target, thisArg, args);
63+
}
64+
5665
return wrapRequestHandler({ options, request, context }, () => Reflect.apply(target, thisArg, args));
5766
},
5867
});

packages/cloudflare/src/pages-plugin.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export function sentryPagesPlugin<
4848
): PagesPluginFunction<Env, Params, Data, PluginParams> {
4949
setAsyncLocalStorageAsyncContextStrategy();
5050
return context => {
51+
if (context.request.method === 'OPTIONS' || context.request.method === 'HEAD') {
52+
return context.next();
53+
}
54+
5155
const options = typeof handlerOrOptions === 'function' ? handlerOrOptions(context) : handlerOrOptions;
5256
return wrapRequestHandler({ options, request: context.request, context: { ...context, props: {} } }, () =>
5357
context.next(),

packages/cloudflare/test/instrumentations/worker/instrumentFetch.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,85 @@ describe('instrumentFetch', () => {
174174
await Promise.all(waits);
175175
expect(flush).toHaveBeenCalledOnce();
176176
});
177+
178+
test('OPTIONS requests bypass SDK instrumentation', async () => {
179+
const originalFetch = vi.fn().mockReturnValue(new Response('options response'));
180+
const handler = {
181+
fetch: originalFetch,
182+
} satisfies ExportedHandler<typeof MOCK_ENV>;
183+
184+
const optionsCallback = vi.fn().mockReturnValue({ dsn: MOCK_ENV.SENTRY_DSN });
185+
186+
const wrappedHandler = withSentry(optionsCallback, handler);
187+
const result = await wrappedHandler.fetch?.(
188+
new Request('https://example.com', { method: 'OPTIONS' }),
189+
MOCK_ENV,
190+
createMockExecutionContext(),
191+
);
192+
193+
expect(originalFetch).toHaveBeenCalledTimes(1);
194+
expect(optionsCallback).not.toHaveBeenCalled();
195+
expect(result?.status).toBe(200);
196+
if (result) {
197+
expect(await result.text()).toBe('options response');
198+
}
199+
});
200+
201+
test('HEAD requests bypass SDK instrumentation', async () => {
202+
const originalFetch = vi.fn().mockReturnValue(new Response('head response'));
203+
const handler = {
204+
fetch: originalFetch,
205+
} satisfies ExportedHandler<typeof MOCK_ENV>;
206+
207+
const optionsCallback = vi.fn().mockReturnValue({ dsn: MOCK_ENV.SENTRY_DSN });
208+
209+
const wrappedHandler = withSentry(optionsCallback, handler);
210+
const result = await wrappedHandler.fetch?.(
211+
new Request('https://example.com', { method: 'HEAD' }),
212+
MOCK_ENV,
213+
createMockExecutionContext(),
214+
);
215+
216+
expect(originalFetch).toHaveBeenCalledTimes(1);
217+
expect(optionsCallback).not.toHaveBeenCalled();
218+
expect(result?.status).toBe(200);
219+
});
220+
221+
test('GET requests are instrumented', async () => {
222+
const originalFetch = vi.fn().mockReturnValue(new Response('get response'));
223+
const handler = {
224+
fetch: originalFetch,
225+
} satisfies ExportedHandler<typeof MOCK_ENV>;
226+
227+
const optionsCallback = vi.fn().mockReturnValue({ dsn: MOCK_ENV.SENTRY_DSN });
228+
229+
const wrappedHandler = withSentry(optionsCallback, handler);
230+
await wrappedHandler.fetch?.(
231+
new Request('https://example.com', { method: 'GET' }),
232+
MOCK_ENV,
233+
createMockExecutionContext(),
234+
);
235+
236+
expect(originalFetch).toHaveBeenCalledTimes(1);
237+
expect(optionsCallback).toHaveBeenCalledTimes(1);
238+
});
239+
240+
test('POST requests are instrumented', async () => {
241+
const originalFetch = vi.fn().mockReturnValue(new Response('post response'));
242+
const handler = {
243+
fetch: originalFetch,
244+
} satisfies ExportedHandler<typeof MOCK_ENV>;
245+
246+
const optionsCallback = vi.fn().mockReturnValue({ dsn: MOCK_ENV.SENTRY_DSN });
247+
248+
const wrappedHandler = withSentry(optionsCallback, handler);
249+
await wrappedHandler.fetch?.(
250+
new Request('https://example.com', { method: 'POST' }),
251+
MOCK_ENV,
252+
createMockExecutionContext(),
253+
);
254+
255+
expect(originalFetch).toHaveBeenCalledTimes(1);
256+
expect(optionsCallback).toHaveBeenCalledTimes(1);
257+
});
177258
});

packages/cloudflare/test/pages-plugin.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,68 @@ describe('sentryPagesPlugin', () => {
5656
expect(result.status).toBe(response.status);
5757
expect(await result.text()).toBe('test');
5858
});
59+
60+
test('OPTIONS requests bypass SDK instrumentation', async () => {
61+
const mockOptionsHandler = vi.fn().mockReturnValue(MOCK_OPTIONS);
62+
const mockOnRequest = sentryPagesPlugin(mockOptionsHandler);
63+
const mockNext = vi.fn().mockResolvedValue(new Response('options response'));
64+
65+
const result = await mockOnRequest({
66+
request: new Request('https://example.com', { method: 'OPTIONS' }),
67+
functionPath: 'test',
68+
waitUntil: vi.fn(),
69+
passThroughOnException: vi.fn(),
70+
next: mockNext,
71+
env: { ASSETS: { fetch: vi.fn() } },
72+
params: {},
73+
data: {},
74+
pluginArgs: MOCK_OPTIONS,
75+
});
76+
77+
expect(mockOptionsHandler).not.toHaveBeenCalled();
78+
expect(mockNext).toHaveBeenCalledTimes(1);
79+
expect(result.status).toBe(200);
80+
});
81+
82+
test('HEAD requests bypass SDK instrumentation', async () => {
83+
const mockOptionsHandler = vi.fn().mockReturnValue(MOCK_OPTIONS);
84+
const mockOnRequest = sentryPagesPlugin(mockOptionsHandler);
85+
const mockNext = vi.fn().mockResolvedValue(new Response('head response'));
86+
87+
const result = await mockOnRequest({
88+
request: new Request('https://example.com', { method: 'HEAD' }),
89+
functionPath: 'test',
90+
waitUntil: vi.fn(),
91+
passThroughOnException: vi.fn(),
92+
next: mockNext,
93+
env: { ASSETS: { fetch: vi.fn() } },
94+
params: {},
95+
data: {},
96+
pluginArgs: MOCK_OPTIONS,
97+
});
98+
99+
expect(mockOptionsHandler).not.toHaveBeenCalled();
100+
expect(mockNext).toHaveBeenCalledTimes(1);
101+
expect(result.status).toBe(200);
102+
});
103+
104+
test('GET requests are instrumented', async () => {
105+
const mockOptionsHandler = vi.fn().mockReturnValue(MOCK_OPTIONS);
106+
const mockOnRequest = sentryPagesPlugin(mockOptionsHandler);
107+
const mockNext = vi.fn().mockResolvedValue(new Response('get response'));
108+
109+
await mockOnRequest({
110+
request: new Request('https://example.com', { method: 'GET' }),
111+
functionPath: 'test',
112+
waitUntil: vi.fn(),
113+
passThroughOnException: vi.fn(),
114+
next: mockNext,
115+
env: { ASSETS: { fetch: vi.fn() } },
116+
params: {},
117+
data: {},
118+
pluginArgs: MOCK_OPTIONS,
119+
});
120+
121+
expect(mockOptionsHandler).toHaveBeenCalledTimes(1);
122+
});
59123
});

packages/node/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@
6868
"@opentelemetry/api": "^1.9.1",
6969
"@opentelemetry/core": "^2.6.1",
7070
"@opentelemetry/instrumentation": "^0.214.0",
71-
"@opentelemetry/instrumentation-graphql": "0.62.0",
7271
"@opentelemetry/instrumentation-http": "0.214.0",
7372
"@opentelemetry/sql-common": "^0.41.2",
7473
"@opentelemetry/instrumentation-pg": "0.66.0",

packages/node/src/integrations/tracing/dataloader/vendored/dataloader-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Simplified types inlined from dataloader.
33
*/
44

5-
declare class DataLoader<K, V, C = K> {
5+
declare class DataLoader<K, V, _C = K> {
66
constructor(batchLoadFn: DataLoader.BatchLoadFn<K, V>, options?: any);
77
load(key: K): Promise<V>;
88
loadMany(keys: ArrayLike<K>): Promise<Array<V | Error>>;

packages/node/src/integrations/tracing/graphql.ts renamed to packages/node/src/integrations/tracing/graphql/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AttributeValue } from '@opentelemetry/api';
22
import { SpanStatusCode } from '@opentelemetry/api';
3-
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
3+
import { GraphQLInstrumentation } from './vendored/instrumentation';
44
import type { IntegrationFn } from '@sentry/core';
55
import { defineIntegration, getRootSpan, spanToJSON } from '@sentry/core';
66
import { addOriginToSpan, generateInstrumentOnce } from '@sentry/node-core';
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-graphql
18+
* - Upstream version: @opentelemetry/instrumentation-graphql@0.66.0
19+
*/
20+
/* eslint-disable */
21+
22+
export enum AllowedOperationTypes {
23+
QUERY = 'query',
24+
MUTATION = 'mutation',
25+
SUBSCRIPTION = 'subscription',
26+
}
27+
28+
export enum TokenKind {
29+
SOF = '<SOF>',
30+
EOF = '<EOF>',
31+
BANG = '!',
32+
DOLLAR = '$',
33+
AMP = '&',
34+
PAREN_L = '(',
35+
PAREN_R = ')',
36+
SPREAD = '...',
37+
COLON = ':',
38+
EQUALS = '=',
39+
AT = '@',
40+
BRACKET_L = '[',
41+
BRACKET_R = ']',
42+
BRACE_L = '{',
43+
PIPE = '|',
44+
BRACE_R = '}',
45+
NAME = 'Name',
46+
INT = 'Int',
47+
FLOAT = 'Float',
48+
STRING = 'String',
49+
BLOCK_STRING = 'BlockString',
50+
COMMENT = 'Comment',
51+
}
52+
53+
export enum SpanNames {
54+
EXECUTE = 'graphql.execute',
55+
PARSE = 'graphql.parse',
56+
RESOLVE = 'graphql.resolve',
57+
VALIDATE = 'graphql.validate',
58+
SCHEMA_VALIDATE = 'graphql.validateSchema',
59+
SCHEMA_PARSE = 'graphql.parseSchema',
60+
}

0 commit comments

Comments
 (0)