You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Only use attributes from [Sentry Gen AI Conventions](https://getsentry.github.io/sentry-conventions/attributes/gen_ai/).
40
-
41
-
## Streaming
42
-
43
-
-**Non-streaming:**`startSpan()`, set attributes from response
44
-
-**Streaming:**`startSpanManual()`, accumulate state via async generator or event listeners, set `GEN_AI_RESPONSE_STREAMING_ATTRIBUTE: true`, call `span.end()` in finally block
Span ops and attributes are specified outside this repo. Never invent or hardcode either:
47
12
48
-
## Token Accumulation
13
+
-[gen_ai attributes](https://getsentry.github.io/sentry-conventions/attributes/gen_ai/) and [gen_ai ops](https://getsentry.github.io/sentry-conventions/ops/#gen_ai) — normative; import from `@sentry/conventions/attributes` and `@sentry/conventions/op`
14
+
-[RFC 0153](https://github.com/getsentry/rfcs/blob/main/text/0153-decoupling-sentrys-generative-ai-conventions-from-open-telemetry.md) — why Sentry's gen-AI conventions diverge from the [OTel gen-ai semconv](https://opentelemetry.io/docs/specs/semconv/gen-ai/)
49
15
50
-
-**Child spans:** Set tokens directly from API response via `setTokenUsageAttributes()`
51
-
-**Parent spans (`invoke_agent`):** Accumulate from children using event processor (see `vercel-ai/`)
16
+
Derive the op with `getGenAiSpanOp()` from `ai/core/utils.ts` rather than picking one by hand. `ai/core/gen-ai-attributes.ts` is for gap-fillers only — keys with no `@sentry/conventions` equivalent — so check it last, not first.
2.**Node.js:** Add `callWhenPatched()` optimization in `packages/node/src/integrations/tracing/{provider}/index.ts` — defers registration until package is imported
59
-
3.**Edge:** Direct registration in `packages/cloudflare/src/integrations/tracing/{provider}.ts` — no OTel, call processors immediately
| 1 — Native tracing channel | the SDK publishes to `diagnostics_channel`|`integrations/vercel-ai/vercel-ai-dc-subscriber.ts`|
31
+
| 2 — Orchestrion channels | the SDK has no telemetry of its own |`integrations/openai.ts` + `orchestrion/config/openai.ts`|
32
+
| 3 — Callback/exporter | the SDK exposes hooks or an exporter |`ai/langchain/`, `ai/mastra/` (exporter-shaped agent framework) |
81
33
82
-
## Auto-Instrumentation (Node.js)
34
+
What the reference files won't tell you:
83
35
84
-
**Mandatory** for Node.js AI integrations. OTel only patches when the package is imported (zero cost if unused).
36
+
- A provider can need two patterns at once: `vercelAIIntegration` subscribes to native `ai:telemetry` for `ai` >= 7 _and_ runs orchestrion injection for v4-v6.
37
+
- Pattern 1 subscribers are safe to register unconditionally — subscribing is a no-op on SDK versions that never publish.
85
38
86
-
### Steps
39
+
##Where The Code Goes
87
40
88
-
1.**Add to `getAutoPerformanceIntegrations()`** in `packages/node/src/integrations/tracing/index.ts` — LangChain MUST come first
89
-
2.**Add to `getOpenTelemetryInstrumentationToPreload()`** for OTel-based integrations
90
-
3.**Export from `packages/node/src/index.ts`**: integration function + options type
- Runtime packages (`node`, `cloudflare`, `bun`, ...) re-export from `@sentry/server-utils` — they never define their own
44
+
- Exception: Workers AI is client-wrapped in `packages/cloudflare/src/instrumentations/worker/instrumentEnv.ts`
95
45
96
-
## Key Rules
46
+
## Gotchas
97
47
98
-
1. Respect `dataCollection.genAI` for recording input and output messages
99
-
2. Set `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'auto.ai.{provider}'` (alphanumerics, `_`, `.` only)
100
-
3. Truncate large data with helper functions from `utils.ts`
101
-
4.`gen_ai.invoke_agent` for parent ops, `gen_ai.chat` for child ops
48
+
1.**Detect streaming from the result shape** — an async-iterable or the SDK's stream object — not from `params.stream`. Only the manual `instrument{Provider}Client()` API keys off `params.stream === true`.
49
+
2.**Never set streamed response attributes by hand.** Accumulate into a `StreamResponseState` and call `endStreamSpan()` (`ai/openai/streaming.ts` for an async generator, `ai/anthropic-ai/streaming.ts` for a listener-based stream).
50
+
3.**Never truncate message payloads.** Truncation was removed in v11 (#23045) and nothing downstream caps them; size limiting is server-side.
51
+
4.**Never roll child token usage up onto parent spans.** Tree totals are computed product-side from the full span tree.
52
+
5.**Never read `dataCollection.genAI` directly.** Gate input/output recording on `resolveAIRecordingOptions()`.
53
+
6.**LangChain must be registered first** in `getTracingIntegrations()`, so it can disable the provider integrations before they instrument.
54
+
7. Set `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = 'auto.ai.{provider}'` (alphanumerics, `_`, `.` only).
102
55
103
56
## Checklist
104
57
105
-
-[ ] Runtime-specific code placed only in that runtime's package
106
-
-[ ] Added to `getAutoPerformanceIntegrations()` in correct order (Node.js)
107
-
-[ ] Added to `getOpenTelemetryInstrumentationToPreload()` (Node.js with OTel)
108
-
-[ ] Exported from appropriate package index
109
-
-[ ] E2E tests added and verifying auto-instrumentation
110
-
-[ ] Only used attributes from [Sentry Gen AI Conventions](https://getsentry.github.io/sentry-conventions/attributes/gen_ai/)
111
-
-[ ] JSDoc says "enabled by default" or "not enabled by default"
112
-
-[ ] Documented how to disable (if auto-enabled)
113
-
-[ ] Verified OTel only patches when package imported (Node.js)
-[ ] Instrumentation in `src/ai/`, integration in `src/integrations/`, registered in `getTracingIntegrations()` (LangChain first)
59
+
-[ ] Exported from `packages/server-utils/src/index.ts`, re-exported from the supported runtime packages
60
+
-[ ] E2E tests in `dev-packages/node-integration-tests/suites/tracing/{provider}/` (and `cloudflare-integration-tests/` if supported)
61
+
-[ ] Ops and attributes from `@sentry/conventions`, op derived via `getGenAiSpanOp()`
62
+
-[ ] Recording gated on `resolveAIRecordingOptions()`; no truncation, no token rollup
63
+
-[ ] JSDoc names the channels subscribed to, the supported SDK versions, and — for Pattern 2 — that it requires the Sentry runtime hook or bundler plugin
64
+
-[ ] Patching happens only once the target package is imported (zero cost if unused)
120
65
121
66
**When in doubt, follow the pattern of the most similar existing integration.**
-`consoleSandbox(() => { console.warn(...) })` for intentional user-facing warnings (e.g. init-time misconfiguration messages). The `consoleSandbox` wrapper prevents the SDK's own console instrumentation from intercepting the call. Bare `console.*` calls outside very early init paths (e.g. before the logger is available) should be flagged.
69
69
- Flag `url.full`, `url.query`, `http.target` or `request.query_string` being set from a URL that isn't filtered. Wrap the value in `filterCollectedUrl()` (or `filterCollectedUrlQuery()` for a bare query string), passing the `client` if one is in scope, so `dataCollection.urlQueryParams` applies. Values that can't contain a query (a bare pathname, a queue URL) are fine. The `sdk/no-unfiltered-url-attributes` lint rule catches direct attribute writes, so look for what it can't: URLs passed through a helper or variable first, deprecated aliases set next to a filtered attribute, and URLs on breadcrumbs or events instead of spans.
70
70
- Flag span names built from a raw URL. Names follow `METHOD scheme://host/path` and must never contain a query string, so they need `stripUrlQueryAndFragment()`, not `filterCollectedUrl()`.
71
+
- Flag a SQL statement that reaches telemetry unsanitized. `db.query.text`, `db.query.summary`, a DB span name, and a breadcrumb carrying a statement all have to come from `sanitizeSqlQuery()` or `sanitizeSqlQueryWithSummary()` (`@sentry/server-utils`). Inline literals are user data, and OTel allows collecting query text only once they are replaced with `?`. This is deliberately not gated on `dataCollection.databaseQueryData`, which does not cover query text.
72
+
- Cover every place the statement lands, not only the span attribute. The two that get forgotten are the breadcrumb beside the span and the span name used when span streaming is off.
73
+
- Sanitize each statement of a batch before joining them.
74
+
- Pass the dialect. `toSqlDialect()` maps a driver or `db.system.name` value to one, and a missing dialect leaves MySQL and SQL Server values in the statement.
75
+
- Leave Redis command text alone. It is not SQL and has its own redaction path.
71
76
- Flag usage of the following APIs: `getCurrentScope()`, `getIsolationScope()`, `getClient()` if they are avoidable. Flag it with severity Low and acknowledge from the start that this is more a "is this necessary" check, rather than a rule violation.
72
77
- Reason for flagging: Usage of these APIs is problematic for multi-client setups where either there is no "current" client/scope, or the wrong client might be used. Calling these APIs would create a current scope, thereby misleading any future calls to these APIs.
73
78
- What to do instead: Use an existing reference to the scope or client. For example, this is possible in most `Integration` hooks.
0 commit comments