@@ -3,6 +3,7 @@ import type { Client } from '../../client';
33import type { ScopeData } from '../../scope' ;
44import {
55 SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT ,
6+ SEMANTIC_ATTRIBUTE_SENTRY_OP ,
67 SEMANTIC_ATTRIBUTE_SENTRY_RELEASE ,
78 SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME ,
89 SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION ,
@@ -79,6 +80,17 @@ export function captureSpan(span: Span, client: Client): SerializedStreamedSpanW
7980 } ) ;
8081 }
8182
83+ // Backfill sentry.op from span attributes when not explicitly set.
84+ // OTel-originated spans don't have sentry.op set — we infer it from semantic conventions.
85+ if ( ! processedSpan . attributes ?. [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] ) {
86+ const inferredOp = inferOpFromAttributes ( processedSpan . attributes ) ;
87+ if ( inferredOp ) {
88+ safeSetSpanJSONAttributes ( processedSpan , {
89+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : inferredOp ,
90+ } ) ;
91+ }
92+ }
93+
8294 return {
8395 ...streamedSpanJsonToSerializedSpan ( processedSpan ) ,
8496 _segmentSpan : segmentSpan ,
@@ -150,3 +162,29 @@ export function safeSetSpanJSONAttributes(
150162 }
151163 } ) ;
152164}
165+
166+ /**
167+ * Infer `sentry.op` from span attributes based on OTel semantic conventions.
168+ * This is needed because OTel-originated spans don't set `sentry.op` — the non-streamed
169+ * path infers it in the `SentrySpanExporter`, but streamed spans skip the exporter entirely.
170+ */
171+ function inferOpFromAttributes ( attributes ?: RawAttributes < Record < string , unknown > > ) : string | undefined {
172+ if ( ! attributes ) {
173+ return undefined ;
174+ }
175+
176+ const httpMethod = attributes [ 'http.request.method' ] || attributes [ 'http.method' ] ;
177+ if ( httpMethod ) {
178+ // Determine client vs server from the span's parent:
179+ // - Spans with a server address are outgoing (client) requests
180+ // - The `sentry.origin` attribute can also indicate the direction
181+ return attributes [ 'server.address' ] || attributes [ 'net.peer.name' ] ? 'http.client' : 'http.server' ;
182+ }
183+
184+ const dbSystem = attributes [ 'db.system.name' ] || attributes [ 'db.system' ] ;
185+ if ( dbSystem ) {
186+ return 'db' ;
187+ }
188+
189+ return undefined ;
190+ }
0 commit comments