Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .agents/skills/add-ai-integration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Reference: `packages/node/src/integrations/tracing/langchain/`

## Key Rules

1. Respect `sendDefaultPii` for `recordInputs`/`recordOutputs`
1. Respect `dataCollection.genAI` for recording input and output messages
2. Set `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'auto.ai.{provider}'` (alphanumerics, `_`, `.` only)
3. Truncate large data with helper functions from `utils.ts`
4. `gen_ai.invoke_agent` for parent ops, `gen_ai.chat` for child ops
Expand Down
68 changes: 34 additions & 34 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,64 +148,64 @@ Sentry.init({

Affected SDKs: All SDKs.

The `sendDefaultPii` option was **removed** and replaced by a more granular `dataCollection` option that controls each category of collected data individually.
> **Heads up — this is a behavior change, not just a renamed option.**
> In v10, leaving `sendDefaultPii` unset behaved like `sendDefaultPii: false` (restrictive).
> In v11, leaving `dataCollection` unset collects the categories below **by default**.
> Review this before upgrading if you'd rather not collect HTTP request data, database queries, or GenAI inputs/outputs.

The **default behaviour is now more permissive**. In v10, with neither `sendDefaultPii` nor `dataCollection` set, the SDK behaved like `sendDefaultPii: false`. In v11, the `dataCollection` defaults apply out of the box:
We've replaced `sendDefaultPii` with `dataCollection`, which controls each category of collected data individually. The default is now more permissive than in v10.

| Category | v10 default (no `sendDefaultPii`) | v11 default |
| --------------------- | --------------------------------- | -------------------- |
| `userInfo` | `false` | `true` |
| `cookies` | sensitive keys denied | `true` |
| `httpHeaders` | sensitive keys denied | request + response |
| `httpBodies` | none (`[]`) | all request/response |
| `urlQueryParams` | sensitive keys denied | `true` |
| `genAI` | inputs/outputs off | inputs + outputs on |
| `stackFrameVariables` | `true` | `true` |
| `frameContextLines` | `5` | `5` |
| Category | v10 default (`sendDefaultPii` off) | v11 default |
| --------------------- | ---------------------------------- | -------------------- |
| `userInfo` | `false` | `true` |
| `cookies` | not collected | `true` |
| `httpHeaders` | request + response, PII scrubbed | request + response |
| `httpBodies` | not collected (size only) | all request/response |
| `urlQueryParams` | `true` | `true` |
| `genAI` | inputs + outputs not collected | inputs + outputs |
| `databaseQueryData` | `false` | `true` |
| `stackFrameVariables` | `true` | `true` |
| `frameContextLines` | `7` | `5` |

> Sensitive values (keys, tokens, auth headers, etc.) are always filtered out regardless of these settings.
> Sentry's built-in sensitive-data filtering still applies. Review your data-scrubbing config for categories that may contain sensitive values — especially request/response bodies.

Migration:
#### If you previously set `sendDefaultPii: true`

The v11 default matches this, so just remove the option:

```js
// before (v10) — collect the default set of PII
Sentry.init({
sendDefaultPii: true,
});
// v10
Sentry.init({ sendDefaultPii: true });

// after (v11) — this is now the default; you can remove the option entirely,
// or opt into specific categories explicitly:
Sentry.init({
dataCollection: {
userInfo: true,
cookies: true,
httpHeaders: { request: true, response: true },
urlQueryParams: true,
genAI: { inputs: true, outputs: true },
},
});
// v11 — same behavior is now the default
Sentry.init({});
```

If you previously relied on the restrictive default (`sendDefaultPii: false` or unset) and want to
keep collecting as little data as possible, you now need to opt out explicitly:
#### If you want to keep the v10 default behavior

Set the baseline explicitly. **Don't leave `dataCollection` unset** — that now enables broader collection.

```js
// after (v11)restrict data collection to the v10-like minimum
// v11 — preserves the v10 default
Sentry.init({
dataCollection: {
userInfo: false,
cookies: false,
httpHeaders: { request: false, response: false },
httpHeaders: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] },
httpBodies: [],
urlQueryParams: false,
urlQueryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] },
genAI: { inputs: false, outputs: false },
databaseQueryData: false,
graphQL: { document: false, variables: false },
},
});
```

Each key-value field (`cookies`, `urlQueryParams`, `httpHeaders.request`, `httpHeaders.response`) accepts
`true`, `false`, `{ allow: string[] }`, or `{ deny: string[] }` for fine-grained control.

See the [`dataCollection` docs](https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection) for the full option list.

#### RequestData

The `requestDataIntegration`'s `include` options remain an integration-level override. An explicit `false`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ Sentry.init({
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
sendDefaultPii: true,
integrations: [Sentry.spanStreamingIntegration()],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ Sentry.init({
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
sendDefaultPii: true,
traceLifecycle: 'stream',
integrations: [Sentry.spanStreamingIntegration()],
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ Sentry.init({
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
sendDefaultPii: true,
traceLifecycle: 'stream',
integrations: [Sentry.vercelAIIntegration(), Sentry.spanStreamingIntegration()],
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,4 @@ Sentry.init({
tracesSampleRate: 1.0,
transport: loggingTransport,
traceLifecycle: 'stream',
// todo(v11): bridge-regression counterpart to instrument-with-datacollection.mjs; remove when sendDefaultPii is dropped in v11
sendDefaultPii: true,
});
9 changes: 5 additions & 4 deletions packages/astro/src/server/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ async function instrumentRequestStartHttpServerSpan(
}

const request = ctx.request;
const client = getClient();
if (!client) {
return next();
}

// Note: We guard outside of this function call that the request is dynamic
// accessing headers on a static route would throw
Expand Down Expand Up @@ -212,10 +216,7 @@ async function instrumentRequestStartHttpServerSpan(
method,
[URL_FULL]: ctx.url.href,
[URL_PATH]: ctx.url.pathname,
...httpHeadersToSpanAttributes(
winterCGHeadersToDict(request.headers),
getClient()?.getDataCollectionOptions() ?? false,
),
...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), client.getDataCollectionOptions()),
};

if (parametrizedRoute) {
Expand Down
8 changes: 6 additions & 2 deletions packages/bun/src/integrations/bunserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ function wrapRequestHandler<T extends RouteHandler = RouteHandler>(
const client = getClient();
const dataCollection = client?.getDataCollectionOptions();

Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), dataCollection));
if (dataCollection) {
Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), dataCollection));
}

isolationScope.setSDKProcessingMetadata({
normalizedRequest: {
Expand Down Expand Up @@ -254,7 +256,9 @@ function wrapRequestHandler<T extends RouteHandler = RouteHandler>(
status_code: response.status,
});

span.setAttributes(httpHeadersToSpanAttributes(response.headers.toJSON(), dataCollection, 'response'));
if (dataCollection) {
span.setAttributes(httpHeadersToSpanAttributes(response.headers.toJSON(), dataCollection, 'response'));
}
}
return response;
} catch (e) {
Expand Down
14 changes: 6 additions & 8 deletions packages/cloudflare/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/work
import {
captureException,
continueTrace,
getClient,
getHttpSpanDetailsFromUrlObject,
httpHeadersToSpanAttributes,
parseStringToURLObject,
Expand Down Expand Up @@ -77,13 +76,12 @@ export function wrapRequestHandler(
attributes['user_agent.original'] = userAgentHeader;
}

Object.assign(
attributes,
httpHeadersToSpanAttributes(
winterCGHeadersToDict(request.headers),
getClient()?.getDataCollectionOptions() ?? false,
),
);
if (client) {
Object.assign(
attributes,
httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), client.getDataCollectionOptions()),
);
}

attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';

Expand Down
7 changes: 1 addition & 6 deletions packages/cloudflare/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ function getRegisteredChannelIntegrations(): Integration[] {

/** Get the default integrations for the Cloudflare SDK. */
export function getDefaultIntegrations(options: CloudflareOptions): Integration[] {
// TODO(v11): Drop this transitional gating and let `requestDataIntegration` rely on the resolved
// `dataCollection` defaults directly. Until then, preserve the historical Cloudflare behavior of not
// attaching cookies unless the user explicitly opts in via `sendDefaultPii` or `dataCollection.cookies`.
// eslint-disable-next-line typescript/no-deprecated
const cookiesEnabled = options.sendDefaultPii || options.dataCollection?.cookies != null;
return [
// The Dedupe integration should not be used in workflows because we want to
// capture all step failures, even if they are the same error.
Expand All @@ -58,7 +53,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[
linkedErrorsIntegration(),
fetchIntegration(),
httpServerIntegration(),
requestDataIntegration(cookiesEnabled ? undefined : { include: { cookies: false } }),
requestDataIntegration(),
consoleIntegration(),
// The orchestrion diagnostics-channel subscribers (mysql, pg, …). The
// `@sentry/cloudflare/vite` plugin injects the channels at build time and,
Expand Down
6 changes: 2 additions & 4 deletions packages/cloudflare/test/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,7 @@ describe('withSentry', () => {
expect(sentryEvent.sdkProcessingMetadata?.normalizedRequest?.data).toEqual(JSON.stringify({ key: 'value' }));
});

// TODO(v11): Cookies should be attached (subject to denylist filtering) by default. Until then we keep the
// historical Cloudflare behavior of not attaching cookies unless the user explicitly opts in.
test('does not capture cookies by default', async () => {
test('captures cookies by default', async () => {
let sentryEvent: Event = {};

await wrapRequestHandler(
Expand All @@ -322,7 +320,7 @@ describe('withSentry', () => {
},
);

expect(sentryEvent.request?.cookies).toBeUndefined();
expect(sentryEvent.request?.cookies).toEqual({ foo: 'bar' });
});

test('does not capture request body for GET requests', async () => {
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/integrations/http/server-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ function buildServerSpanWrap(
return next();
}

const dataCollectionOptions = client.getDataCollectionOptions();

if (
shouldIgnoreSpansForIncomingRequest(request, {
ignoreStaticAssets,
Expand Down Expand Up @@ -308,7 +310,7 @@ function buildServerSpanWrap(
'http.flavor': httpVersion,
'net.transport': httpVersion?.toUpperCase() === 'QUIC' ? 'ip_udp' : 'ip_tcp',
...getRequestContentLengthAttribute(request),
...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, client.getDataCollectionOptions()),
...httpHeadersToSpanAttributes(normalizedRequest.headers || {}, dataCollectionOptions),
},
},
span => {
Expand All @@ -330,7 +332,7 @@ function buildServerSpanWrap(
'http.status_code': response.statusCode,
...httpHeadersToSpanAttributes(
headersToDict(response.headers),
client?.getDataCollectionOptions() ?? false,
dataCollectionOptions,
'response',
),
});
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/integrations/mcp-server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,9 @@ export type SessionData = {
* Options for configuring the MCP server wrapper.
*/
export type McpServerWrapperOptions = {
/** Whether to capture tool/prompt input arguments in spans. Defaults to `dataCollection.genAI.inputs` (or `sendDefaultPii` when `dataCollection` is not configured). */
/** Whether to capture tool/prompt input arguments in spans. Defaults to `dataCollection.genAI.inputs`. */
recordInputs?: boolean;
/** Whether to capture tool/prompt output results in spans. Defaults to `dataCollection.genAI.outputs` (or `sendDefaultPii` when `dataCollection` is not configured). */
/** Whether to capture tool/prompt output results in spans. Defaults to `dataCollection.genAI.outputs`. */
recordOutputs?: boolean;
};

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/tracing/langchain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
export interface LangChainOptions {
/**
* Whether to record input messages/prompts
* @default false (respects `dataCollection.genAI.inputs`, or `sendDefaultPii` when `dataCollection` is not configured)
* @default false (respects `dataCollection.genAI.inputs`)
*/
recordInputs?: boolean;

/**
* Whether to record output text and responses
* @default false (respects `dataCollection.genAI.outputs`, or `sendDefaultPii` when `dataCollection` is not configured)
* @default false (respects `dataCollection.genAI.outputs`)
*/
recordOutputs?: boolean;

Expand Down
18 changes: 1 addition & 17 deletions packages/core/src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,27 +370,11 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
*/
tunnel?: string;

/**
* Controls if potentially sensitive data should be sent to Sentry by default.
* Note that this only applies to data that the SDK is sending by default
* but not data that was explicitly set (e.g. by calling `Sentry.setUser()`).
*
* @default false
*
* @deprecated Use the {@link ClientOptions.dataCollection} option instead, which lets you control
* each category of collected data individually. `sendDefaultPii` will be removed in the next major
* version (v11). For backwards compatibility, setting `sendDefaultPii: true` currently behaves like
* enabling all `dataCollection` categories. If both `sendDefaultPii` and `dataCollection` are set,
* `sendDefaultPii` will be ignored.
*/
sendDefaultPii?: boolean;

/**
* Controls what data the SDK collects and sends to Sentry.
* All fields are optional — omitted fields use the documented defaults.
*
* This replaces the deprecated {@link ClientOptions.sendDefaultPii} option and lets you control
* each category of collected data (user info, cookies, headers, query params, request/response
* Configure each category of collected data (user info, cookies, headers, query params, request/response
* bodies, gen AI inputs/outputs, etc.) individually.
*/
dataCollection?: DataCollection;
Expand Down

This file was deleted.

Loading
Loading