Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b789608
test(ember): Un-skip ember embroider test (#17295)
mydea Aug 4, 2025
94d0165
ref(browser): Adjust `mechanism.type` in globalHandlersIntegration (#…
Lms24 Aug 4, 2025
ffa8dd8
feat(core): Accumulate tokens for `gen_ai.invoke_agent` spans from ch…
RulaKhaled Aug 4, 2025
94b21c4
ref(feedback): Remove almost duplicate `sendFeedback` rejection messa…
mydea Aug 4, 2025
f866c82
Merge pull request #17302 from getsentry/master
github-actions[bot] Aug 4, 2025
5bf9fd7
feat(solidstart): Streamline build logs (#17304)
mydea Aug 4, 2025
c21df26
fix(replay): Call `sendBufferedReplayOrFlush` when opening/sending fe…
billyvg Aug 4, 2025
a1efb5e
feat(react-router): Streamline build logs (#17303)
mydea Aug 4, 2025
815fc27
feat(nuxt): Do not inject trace meta-tags on cached HTML pages (#17305)
s1gr1d Aug 4, 2025
97f67fc
feat(core): Add `ignoreSpans` option (#17078)
mydea Aug 4, 2025
190e068
chore: Update v10 todos to v11 (#17311)
andreiborza Aug 4, 2025
bcaca08
chore(aws): Remove manual span creation (#17310)
msonnb Aug 5, 2025
8e888bf
feat(deps): Bump @sentry/webpack-plugin from 4.0.0 to 4.0.2 (#17314)
dependabot[bot] Aug 5, 2025
69d2eba
feat(deps): Bump @prisma/instrumentation from 6.12.0 to 6.13.0 (#17315)
dependabot[bot] Aug 5, 2025
9e73f23
feat(deps): Bump @sentry/rollup-plugin from 4.0.0 to 4.0.2 (#17317)
dependabot[bot] Aug 5, 2025
ef3cea0
chore(changelog): Add entry for `ignoreSpans` (#17313)
Lms24 Aug 5, 2025
64d1a46
feat(astro): Streamline build logs (#17301)
mydea Aug 5, 2025
7264677
feat(nuxt): Streamline build logs (#17308)
mydea Aug 5, 2025
f9181d2
fix(nestjs): Add missing `sentry.origin` span attribute to `SentryTra…
Lms24 Aug 5, 2025
6fca6d2
feat(deps): Bump @sentry/cli from 2.50.0 to 2.50.2 (#17316)
dependabot[bot] Aug 5, 2025
873fc7b
fix(node): Assign default export of `openai` to the instrumented fn (…
RulaKhaled Aug 5, 2025
d02c59a
test(e2e): Add `tsx` + `express` e2e test app (#16578)
Lms24 Aug 5, 2025
6d451e9
chore(repo): Add Cursor Bugbot PR Review rules (#17326)
Lms24 Aug 5, 2025
50cce74
meta: Re-organize changelog to add v8 page (#17327)
AbhiPrasad Aug 5, 2025
82f0cf9
chore: Correct title for v8 changelog (#17329)
AbhiPrasad Aug 5, 2025
aab4276
feat(browser): Handles data URIs in chrome stack frames (#17292)
timfish Aug 5, 2025
5ae054c
ref(core): Add more specific exception mechanism for internal errors …
Lms24 Aug 6, 2025
ac57eb1
ref(google-cloud-serverless): Add `mechanism.type` to captured errors…
Lms24 Aug 6, 2025
ce66380
feat(react-router): Add support for Hydrogen with RR7 (#17145)
onurtemizkan Aug 6, 2025
ed07836
feat(cloudflare,vercel-edge): Add support for OpenAI instrumentation …
andreiborza Aug 6, 2025
bcbb6af
meta(changelog): Update changelog for 10.2.0
andreiborza Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .cursor/BUGBOT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# PR Review Guidelines for Cursor Bot

You are reviewing a pull request for the Sentry JavaScript SDK.
Flag any of the following indicators or missing requirements.
If you find anything to flag, mention that you flagged this in the review because it was mentioned in this rules file.
These issues are only relevant for production code.
Do not flag the issues below if they appear in tests.

## Critical Issues to Flag

### Security Vulnerabilities

- Exposed secrets, API keys, tokens or creentials in code or comments
- Unsafe use of `eval()`, `Function()`, or `innerHTML`
- Unsafe regular expressions that could cause ReDoS attacks

### Breaking Changes

- Public API changes without proper deprecation notices
- Removal of publicly exported functions, classes, or types. Internal removals are fine!
- Changes to function signatures in public APIs

## SDK-relevant issues

### Performance Issues

- Multiple loops over the same array (for example, using `.filter`, .`foreach`, chained). Suggest a classic `for` loop as a replacement.
- Memory leaks from event listeners, timers, or closures not being cleaned up or unsubscribing
- Large bundle size increases in browser packages. Sometimes they're unavoidable but flag them anyway.

### Auto instrumentation, SDK integrations, Sentry-specific conventions

- When calling any `startSpan` API (`startInactiveSpan`, `startSpanManual`, etc), always ensure that the following span attributes are set:
- `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN` (`'sentry.origin'`) with a proper span origin
- `SEMANTIC_ATTRIBUTE_SENTRY_OP` (`'sentry.op'`) with a proper span op
- When calling `captureException`, always make sure that the `mechanism` is set:
- `handled`: must be set to `true` or `false`
- `type`: must be set to a proper origin (i.e. identify the integration and part in the integration that caught the exception).
- The type should align with the `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN` if a span wraps the `captureException` call.
- If there's no direct span that's wrapping the captured exception, apply a proper `type` value, following the same naming
convention as the `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN` value.
- When calling `startSpan`, check if error cases are handled. If flag that it might make sense to try/catch and call `captureException`.
3,219 changes: 148 additions & 3,071 deletions CHANGELOG.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sentryTest('handles fetch network errors @firefox', async ({ getLocalTestUrl, pa
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
});
});
Expand All @@ -51,7 +51,7 @@ sentryTest('handles fetch network errors on subdomains @firefox', async ({ getLo
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
});
});
Expand All @@ -78,7 +78,7 @@ sentryTest('handles fetch invalid header name errors @firefox', async ({ getLoca
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
stacktrace: {
frames: expect.any(Array),
Expand Down Expand Up @@ -110,7 +110,7 @@ sentryTest('handles fetch invalid header value errors @firefox', async ({ getLoc
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
stacktrace: {
frames: expect.any(Array),
Expand Down Expand Up @@ -152,7 +152,7 @@ sentryTest('handles fetch invalid URL scheme errors @firefox', async ({ getLocal
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
stacktrace: {
frames: expect.any(Array),
Expand Down Expand Up @@ -184,7 +184,7 @@ sentryTest('handles fetch credentials in url errors @firefox', async ({ getLocal
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
stacktrace: {
frames: expect.any(Array),
Expand Down Expand Up @@ -215,7 +215,7 @@ sentryTest('handles fetch invalid mode errors @firefox', async ({ getLocalTestUr
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
stacktrace: {
frames: expect.any(Array),
Expand Down Expand Up @@ -245,7 +245,7 @@ sentryTest('handles fetch invalid request method errors @firefox', async ({ getL
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
stacktrace: {
frames: expect.any(Array),
Expand Down Expand Up @@ -277,7 +277,7 @@ sentryTest(
value: error,
mechanism: {
handled: false,
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
},
stacktrace: {
frames: expect.any(Array),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ sentryTest(
type: 'Error',
value: 'Object captured as exception with keys: otherKey, type',
mechanism: {
type: 'onerror',
type: 'auto.browser.global_handlers.onerror',
handled: false,
},
stacktrace: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ sentryTest('should catch syntax errors', async ({ getLocalTestUrl, page, browser
type: 'SyntaxError',
value: "Failed to execute 'appendChild' on 'Node': Unexpected token '{'",
mechanism: {
type: 'onerror',
type: 'auto.browser.global_handlers.onerror',
handled: false,
},
stacktrace: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ sentryTest('should catch thrown errors', async ({ getLocalTestUrl, page, browser
type: 'Error',
value: 'realError',
mechanism: {
type: 'onerror',
type: 'auto.browser.global_handlers.onerror',
handled: false,
},
stacktrace: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ sentryTest('should catch thrown objects', async ({ getLocalTestUrl, page, browse
type: 'Error',
value: 'Object captured as exception with keys: error, somekey',
mechanism: {
type: 'onerror',
type: 'auto.browser.global_handlers.onerror',
handled: false,
},
stacktrace: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ sentryTest('should catch thrown strings', async ({ getLocalTestUrl, page, browse
type: 'Error',
value: 'stringError',
mechanism: {
type: 'onerror',
type: 'auto.browser.global_handlers.onerror',
handled: false,
},
stacktrace: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ sentryTest(
type: 'Error',
value: 'promiseError',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
stacktrace: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ sentryTest('should capture a random Event with type unhandledrejection', async (
type: 'Event',
value: 'Event `Event` (type=unhandledrejection) captured as promise rejection',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sentryTest('should catch thrown errors', async ({ getLocalTestUrl, page }) => {
type: 'Error',
value: 'promiseError',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
stacktrace: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sentryTest('should catch thrown strings', async ({ getLocalTestUrl, page }) => {
type: 'UnhandledRejection',
value: 'Non-Error promise rejection captured with value: null',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sentryTest('should catch thrown strings', async ({ getLocalTestUrl, page }) => {
type: 'UnhandledRejection',
value: 'Non-Error promise rejection captured with value: 123',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sentryTest('should capture unhandledrejection with a complex object', async ({ g
type: 'UnhandledRejection',
value: 'Object captured as promise rejection with keys: a, b, c, d, e',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sentryTest('should capture unhandledrejection with an object', async ({ getLocal
type: 'UnhandledRejection',
value: 'Object captured as promise rejection with keys: a, b, c',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sentryTest('should catch thrown strings', async ({ getLocalTestUrl, page }) => {
type: 'UnhandledRejection',
value: 'Non-Error promise rejection captured with value: stringError',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sentryTest('should catch thrown strings', async ({ getLocalTestUrl, page }) => {
type: 'UnhandledRejection',
value: 'Non-Error promise rejection captured with value: undefined',
mechanism: {
type: 'onunhandledrejection',
type: 'auto.browser.global_handlers.onunhandledrejection',
handled: false,
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as Sentry from '@sentry/cloudflare';
import { MockOpenAi } from './mocks';

interface Env {
SENTRY_DSN: string;
}

const mockClient = new MockOpenAi({
apiKey: 'mock-api-key',
});

const client = Sentry.instrumentOpenAiClient(mockClient);

export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
{
async fetch(_request, _env, _ctx) {
const response = await client.chat?.completions?.create({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'What is the capital of France?' },
],
temperature: 0.7,
max_tokens: 100,
});

return new Response(JSON.stringify(response));
},
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { OpenAiClient } from '@sentry/core';

export class MockOpenAi implements OpenAiClient {
public chat?: Record<string, unknown>;
public apiKey: string;

public constructor(config: { apiKey: string }) {
this.apiKey = config.apiKey;

this.chat = {
completions: {
create: async (...args: unknown[]) => {
const params = args[0] as { model: string; stream?: boolean };
// Simulate processing time
await new Promise(resolve => setTimeout(resolve, 10));

if (params.model === 'error-model') {
const error = new Error('Model not found');
(error as unknown as { status: number }).status = 404;
(error as unknown as { headers: Record<string, string> }).headers = { 'x-request-id': 'mock-request-123' };
throw error;
}

return {
id: 'chatcmpl-mock123',
object: 'chat.completion',
created: 1677652288,
model: params.model,
system_fingerprint: 'fp_44709d6fcb',
choices: [
{
index: 0,
message: {
role: 'assistant',
content: 'Hello from OpenAI mock!',
},
finish_reason: 'stop',
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 15,
total_tokens: 25,
},
};
},
},
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { expect, it } from 'vitest';
import { createRunner } from '../../../runner';

// These tests are not exhaustive because the instrumentation is
// already tested in the node integration tests and we merely
// want to test that the instrumentation does not break in our
// cloudflare SDK.

it('traces a basic chat completion request', async () => {
const runner = createRunner(__dirname)
.ignore('event')
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1];

expect(transactionEvent.transaction).toBe('GET /');
expect(transactionEvent.spans).toEqual(
expect.arrayContaining([
expect.objectContaining({
data: expect.objectContaining({
'gen_ai.operation.name': 'chat',
'sentry.op': 'gen_ai.chat',
'gen_ai.system': 'openai',
'gen_ai.request.model': 'gpt-3.5-turbo',
'gen_ai.request.temperature': 0.7,
'gen_ai.response.model': 'gpt-3.5-turbo',
'gen_ai.response.id': 'chatcmpl-mock123',
'gen_ai.usage.input_tokens': 10,
'gen_ai.usage.output_tokens': 15,
'gen_ai.usage.total_tokens': 25,
'gen_ai.response.finish_reasons': '["stop"]',
}),
description: 'chat gpt-3.5-turbo',
op: 'gen_ai.chat',
origin: 'manual',
}),
]),
);
})
.start();
await runner.makeRequest('get', '/');
await runner.completed();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "worker-name",
"compatibility_date": "2025-06-17",
"main": "index.ts",
"compatibility_flags": ["nodejs_compat"]
}
6 changes: 6 additions & 0 deletions dev-packages/cloudflare-integration-tests/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export default defineConfig({
// already run in their own processes. We use threads instead because the
// overhead is significantly less.
pool: 'threads',
// Run tests sequentially to avoid port conflicts with wrangler dev processes
poolOptions: {
threads: {
singleThread: true,
},
},
reporters: process.env.DEBUG
? ['default', { summary: false }]
: process.env.GITHUB_ACTIONS
Expand Down
Loading
Loading