Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: Adopt bindTracingChannelToSpan across runtimes#21642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
77f9ac9a111688732a37018cf01075729dcbee3c83File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,14 @@ | ||
| import { tracingChannel } from 'node:diagnostics_channel'; | ||
| import { | ||
| captureException, | ||
| flushIfServerless, | ||
| GLOBAL_OBJ, | ||
| SEMANTIC_ATTRIBUTE_CACHE_HIT, | ||
| SEMANTIC_ATTRIBUTE_CACHE_KEY, | ||
| SEMANTIC_ATTRIBUTE_SENTRY_OP, | ||
| SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, | ||
| SPAN_STATUS_ERROR, | ||
| SPAN_STATUS_OK, | ||
| startSpanManual, | ||
| startInactiveSpan, | ||
| } from '@sentry/core'; | ||
| import { tracingChannel, type TracingChannelContextWithSpan } from '@sentry/opentelemetry/tracing-channel'; | ||
| import { bindTracingChannelToSpan } from '@sentry/server-utils'; | ||
| import type { TraceContext } from 'unstorage/tracing'; | ||
| const ORIGIN = 'auto.cache.nitro'; | ||
| @@ -57,11 +55,12 @@ function setupStorageTracingChannel(operation: TracedOperation): void { | ||
| const keys = (data: TraceContext): string[] => data.keys ?? []; | ||
| const mountBase = (data: TraceContext): string => (data.base ?? '').replace(/:$/, ''); | ||
| const channel = tracingChannel<TraceContext>(`unstorage.${operation}`, data => { | ||
| const cacheKeys = keys(data); | ||
| bindTracingChannelToSpan( | ||
| tracingChannel<TraceContext>(`unstorage.${operation}`), | ||
| data => { | ||
| const cacheKeys = keys(data); | ||
| return startSpanManual( | ||
| { | ||
| return startInactiveSpan({ | ||
| name: cacheKeys.join(', ') || operation, | ||
| attributes: { | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `cache.${normalizeMethodName(operation)}`, | ||
| @@ -71,34 +70,24 @@ function setupStorageTracingChannel(operation: TracedOperation): void { | ||
| 'db.collection.name': mountBase(data), | ||
| 'db.system.name': data.driver?.name ?? 'unknown', | ||
| }, | ||
| }, | ||
| span => span, | ||
| ); | ||
| }); | ||
| channel.subscribe({ | ||
| asyncEnd(data: TracingChannelContextWithSpan<TraceContext & { result?: unknown }>) { | ||
| if (data._sentrySpan && CACHE_HIT_OPERATIONS.has(operation)) { | ||
| const hit = operation === 'hasItem' ? Boolean(data.result) : isCacheHit(data.keys?.[0], data.result); | ||
| data._sentrySpan.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, hit); | ||
| } | ||
| data._sentrySpan?.setStatus({ code: SPAN_STATUS_OK }); | ||
| data._sentrySpan?.end(); | ||
| void flushIfServerless(); | ||
| }, | ||
| error(data: TracingChannelContextWithSpan<TraceContext & { error?: unknown }>) { | ||
| captureException(data.error, { | ||
| mechanism: { handled: false, type: ORIGIN }, | ||
| }); | ||
| data._sentrySpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); | ||
| data._sentrySpan?.end(); | ||
| void flushIfServerless(); | ||
| }, | ||
| }); | ||
| { | ||
| beforeSpanEnd(span, data) { | ||
| // Error status is set by the binding; the error itself is captured at the request boundary, | ||
| // not here (cache ops aren't an error boundary). Only enrich the success path. | ||
| if (!('error' in data)) { | ||
| const result = (data as { result?: unknown }).result; | ||
| if (CACHE_HIT_OPERATIONS.has(operation)) { | ||
| const hit = operation === 'hasItem' ? Boolean(result) : isCacheHit(data.keys?.[0], result); | ||
| span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, hit); | ||
| } | ||
| } | ||
| void flushIfServerless(); | ||
| }, | ||
| }, | ||
| ); | ||
cursor[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. cursor[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. cursor[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| function normalizeMethodName(methodName: string): string { | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: The refactoring to
bindTracingChannelToSpanfor storage events unintentionally changes the error mechanism type, losing the specific'auto.cache.nitro'context.Severity: LOW
Suggested Fix
Preserve the original error mechanism type for storage events by providing a custom
captureErrorfunction when callingbindTracingChannelToSpan, similar to how HTTP events are handled. This function should return a mechanism object with the type set to'auto.cache.nitro'. For example:captureError: () => ({ mechanism: { handled: false, type: 'auto.cache.nitro' } }).Prompt for AI Agent
Did we get this right? 👍 / 👎 to inform future reviews.