@@ -2,8 +2,10 @@ import type { RawAttributes } from '../../attributes';
22import type { Client } from '../../client' ;
33import type { ScopeData } from '../../scope' ;
44import {
5+ SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME ,
56 SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT ,
67 SEMANTIC_ATTRIBUTE_SENTRY_OP ,
8+ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
79 SEMANTIC_ATTRIBUTE_SENTRY_RELEASE ,
810 SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME ,
911 SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION ,
@@ -15,6 +17,7 @@ import {
1517 SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS ,
1618 SEMANTIC_ATTRIBUTE_USER_USERNAME ,
1719} from '../../semanticAttributes' ;
20+ import { getSanitizedUrlString , parseUrl , stripUrlQueryAndFragment } from '../../utils/url' ;
1821import type { SerializedStreamedSpan , Span , StreamedSpanJSON } from '../../types-hoist/span' ;
1922import { getCombinedScopeData } from '../../utils/scopeData' ;
2023import {
@@ -168,6 +171,7 @@ const SPAN_KIND_CLIENT = 2;
168171 * This mirrors what the `SentrySpanExporter` does for non-streamed spans via `getSpanData`/`inferSpanData`.
169172 * Streamed spans skip the exporter, so we do the inference here during capture.
170173 *
174+ * Backfills: `sentry.op`, `sentry.source`, and `name` (description).
171175 * Uses `safeSetSpanJSONAttributes` so explicitly set attributes are never overwritten.
172176 */
173177function inferSpanDataFromOtelAttributes ( spanJSON : StreamedSpanJSON , spanKind ?: number ) : void {
@@ -178,49 +182,107 @@ function inferSpanDataFromOtelAttributes(spanJSON: StreamedSpanJSON, spanKind?:
178182
179183 const httpMethod = attributes [ 'http.request.method' ] || attributes [ 'http.method' ] ;
180184 if ( httpMethod ) {
181- const opParts = [ 'http' ] ;
182- if ( spanKind === SPAN_KIND_CLIENT ) {
183- opParts . push ( 'client' ) ;
184- } else if ( spanKind === SPAN_KIND_SERVER ) {
185- opParts . push ( 'server' ) ;
186- }
187-
188- if ( attributes [ 'sentry.http.prefetch' ] ) {
189- opParts . push ( 'prefetch' ) ;
190- }
191-
192- safeSetSpanJSONAttributes ( spanJSON , {
193- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : opParts . join ( '.' ) ,
194- } ) ;
185+ inferHttpSpanData ( spanJSON , attributes , spanKind , httpMethod ) ;
195186 return ;
196187 }
197188
198189 const dbSystem = attributes [ 'db.system.name' ] || attributes [ 'db.system' ] ;
199190 if ( dbSystem ) {
200- safeSetSpanJSONAttributes ( spanJSON , {
201- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'db' ,
202- } ) ;
191+ inferDbSpanData ( spanJSON , attributes ) ;
203192 return ;
204193 }
205194
206195 if ( attributes [ 'rpc.service' ] ) {
207- safeSetSpanJSONAttributes ( spanJSON , {
208- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'rpc' ,
209- } ) ;
196+ safeSetSpanJSONAttributes ( spanJSON , { [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'rpc' } ) ;
210197 return ;
211198 }
212199
213200 if ( attributes [ 'messaging.system' ] ) {
214- safeSetSpanJSONAttributes ( spanJSON , {
215- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'message' ,
216- } ) ;
201+ safeSetSpanJSONAttributes ( spanJSON , { [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'message' } ) ;
217202 return ;
218203 }
219204
220205 const faasTrigger = attributes [ 'faas.trigger' ] ;
221206 if ( faasTrigger ) {
222- safeSetSpanJSONAttributes ( spanJSON , {
223- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : `${ faasTrigger } ` ,
224- } ) ;
207+ safeSetSpanJSONAttributes ( spanJSON , { [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : `${ faasTrigger } ` } ) ;
208+ }
209+ }
210+
211+ function inferHttpSpanData (
212+ spanJSON : StreamedSpanJSON ,
213+ attributes : RawAttributes < Record < string , unknown > > ,
214+ spanKind : number | undefined ,
215+ httpMethod : unknown ,
216+ ) : void {
217+ // Infer op: http.client, http.server, or just http
218+ const opParts = [ 'http' ] ;
219+ if ( spanKind === SPAN_KIND_CLIENT ) {
220+ opParts . push ( 'client' ) ;
221+ } else if ( spanKind === SPAN_KIND_SERVER ) {
222+ opParts . push ( 'server' ) ;
223+ }
224+ if ( attributes [ 'sentry.http.prefetch' ] ) {
225+ opParts . push ( 'prefetch' ) ;
226+ }
227+ safeSetSpanJSONAttributes ( spanJSON , { [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : opParts . join ( '.' ) } ) ;
228+
229+ // If the user already set a custom name or source, don't overwrite
230+ if (
231+ attributes [ SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME ] ||
232+ attributes [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] === 'custom'
233+ ) {
234+ return ;
235+ }
236+
237+ // Infer name and source from URL attributes
238+ const httpRoute = attributes [ 'http.route' ] ;
239+ const httpTarget = attributes [ 'http.target' ] ;
240+ const httpUrl = attributes [ 'url.full' ] || attributes [ 'http.url' ] ;
241+ const parsedUrl = typeof httpUrl === 'string' ? parseUrl ( httpUrl ) : undefined ;
242+ const sanitizedUrl = parsedUrl ? getSanitizedUrlString ( parsedUrl ) : undefined ;
243+
244+ let urlPath : string | undefined ;
245+ let source : string | undefined ;
246+
247+ if ( typeof httpRoute === 'string' ) {
248+ urlPath = httpRoute ;
249+ source = 'route' ;
250+ } else if ( spanKind === SPAN_KIND_SERVER && typeof httpTarget === 'string' ) {
251+ urlPath = stripUrlQueryAndFragment ( httpTarget ) ;
252+ source = 'url' ;
253+ } else if ( sanitizedUrl ) {
254+ urlPath = sanitizedUrl ;
255+ source = 'url' ;
256+ } else if ( typeof httpTarget === 'string' ) {
257+ urlPath = stripUrlQueryAndFragment ( httpTarget ) ;
258+ source = 'url' ;
259+ }
260+
261+ if ( urlPath ) {
262+ const isClientOrServer = spanKind === SPAN_KIND_CLIENT || spanKind === SPAN_KIND_SERVER ;
263+ const origin = attributes [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] || 'manual' ;
264+ const isAutoSpan = `${ origin } ` . startsWith ( 'auto' ) ;
265+
266+ if ( isClientOrServer || isAutoSpan ) {
267+ spanJSON . name = `${ httpMethod } ${ urlPath } ` ;
268+ safeSetSpanJSONAttributes ( spanJSON , { [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : source } ) ;
269+ }
270+ }
271+ }
272+
273+ function inferDbSpanData ( spanJSON : StreamedSpanJSON , attributes : RawAttributes < Record < string , unknown > > ) : void {
274+ safeSetSpanJSONAttributes ( spanJSON , { [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'db' } ) ;
275+
276+ if (
277+ attributes [ SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME ] ||
278+ attributes [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] === 'custom'
279+ ) {
280+ return ;
281+ }
282+
283+ const statement = attributes [ 'db.statement' ] ;
284+ if ( statement ) {
285+ spanJSON . name = `${ statement } ` ;
286+ safeSetSpanJSONAttributes ( spanJSON , { [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'task' } ) ;
225287 }
226288}
0 commit comments