@@ -637,7 +637,7 @@ export function _addResourceSpans(
637637 startTime : number ,
638638 duration : number ,
639639 timeOrigin : number ,
640- ignoreResourceSpans ?: Array < string > ,
640+ ignoredResourceSpanOps ?: Array < string > ,
641641) : void {
642642 // we already instrument based on fetch and xhr, so we don't need to
643643 // duplicate spans here.
@@ -646,31 +646,15 @@ export function _addResourceSpans(
646646 }
647647
648648 const op = entry . initiatorType ? `resource.${ entry . initiatorType } ` : 'resource.other' ;
649- if ( ignoreResourceSpans ?. includes ( op ) ) {
649+ if ( ignoredResourceSpanOps ?. includes ( op ) ) {
650650 return ;
651651 }
652652
653- const parsedUrl = parseUrl ( resourceUrl ) ;
654-
655653 const attributes : SpanAttributes = {
656654 [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.resource.browser.metrics' ,
657655 } ;
658- setResourceEntrySizeData ( attributes , entry , 'transferSize' , 'http.response_transfer_size' ) ;
659- setResourceEntrySizeData ( attributes , entry , 'encodedBodySize' , 'http.response_content_length' ) ;
660- setResourceEntrySizeData ( attributes , entry , 'decodedBodySize' , 'http.decoded_response_content_length' ) ;
661-
662- // `deliveryType` is experimental and does not exist everywhere
663- const deliveryType = ( entry as { deliveryType ?: 'cache' | 'navigational-prefetch' | '' } ) . deliveryType ;
664- if ( deliveryType != null ) {
665- attributes [ 'http.response_delivery_type' ] = deliveryType ;
666- }
667656
668- // Types do not reflect this property yet
669- const renderBlockingStatus = ( entry as { renderBlockingStatus ?: 'render-blocking' | 'non-render-blocking' } )
670- . renderBlockingStatus ;
671- if ( renderBlockingStatus ) {
672- attributes [ 'resource.render_blocking_status' ] = renderBlockingStatus ;
673- }
657+ const parsedUrl = parseUrl ( resourceUrl ) ;
674658
675659 if ( parsedUrl . protocol ) {
676660 attributes [ 'url.scheme' ] = parsedUrl . protocol . split ( ':' ) . pop ( ) ; // the protocol returned by parseUrl includes a :, but OTEL spec does not, so we remove it.
@@ -690,6 +674,49 @@ export function _addResourceSpans(
690674 attributes [ 'network.protocol.version' ] = version ;
691675 }
692676
677+ setResourceRequestAttributes ( entry , attributes , [
678+ // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStatus
679+ [ 'responseStatus' , 'http.request.response_status' ] ,
680+
681+ // Timing attributes (request/response lifecycle)
682+ // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming#timestamps
683+ [ 'redirectStart' , 'http.request.redirect_start' ] ,
684+ [ 'redirectEnd' , 'http.request.redirect_end' ] ,
685+
686+ [ 'workerStart' , 'http.request.worker_start' ] ,
687+
688+ [ 'fetchStart' , 'http.request.fetch_start' ] ,
689+
690+ [ 'domainLookupStart' , 'http.request.domain_lookup_start' ] ,
691+ [ 'domainLookupEnd' , 'http.request.domain_lookup_end' ] ,
692+
693+ [ 'connectStart' , 'http.request.connect_start' ] ,
694+ [ 'secureConnectionStart' , 'http.request.secure_connection_start' ] ,
695+ [ 'connectEnd' , 'http.request.connect_end' ] ,
696+
697+ [ 'requestStart' , 'http.request.request_start' ] ,
698+ [ 'firstInterimResponseStart' , 'http.response.first_interim_response_start' ] ,
699+ [ 'finalResponseHeadersStart' , 'http.response.final_response_headers_start' ] ,
700+
701+ // responseStart can also be interpreted as TTFB for resource requests: https://web.dev/articles/ttfb
702+ [ 'responseStart' , 'http.response.response_start' ] ,
703+ [ 'responseEnd' , 'http.response.response_end' ] ,
704+
705+ // Size attributes:
706+ // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/transferSize
707+ // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/encodedBodySize
708+ // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/decodedBodySize
709+ [ 'transferSize' , 'http.response_transfer_size' ] ,
710+ [ 'encodedBodySize' , 'http.response_content_length' ] ,
711+ [ 'decodedBodySize' , 'http.decoded_response_content_length' ] ,
712+
713+ // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/renderBlockingStatus
714+ [ 'renderBlockingStatus' , 'resource.render_blocking_status' ] ,
715+
716+ // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/deliveryType
717+ [ 'deliveryType' , 'http.response_delivery_type' ] ,
718+ ] ) ;
719+
693720 const startTimestamp = timeOrigin + startTime ;
694721 const endTimestamp = startTimestamp + duration ;
695722
@@ -776,16 +803,34 @@ function _setWebVitalAttributes(span: Span, options: AddPerformanceEntriesOption
776803 }
777804}
778805
779- function setResourceEntrySizeData (
806+ type ExperimentalResourceTimingProperty =
807+ | 'firstInterimResponseStart'
808+ | 'finalResponseHeadersStart'
809+ | 'renderBlockingStatus'
810+ | 'deliveryType' ;
811+
812+ /**
813+ * Use this to set any attributes we can take directly form the PerformanceResourceTiming entry.
814+ *
815+ * This is just a mapping function for entry->attribute to keep bundle-size minimal.
816+ * Experimental properties are also accepted (see {@link ExperimentalResourceTimingProperty}).
817+ * Assumes that all entry properties might be undefined for browser-specific differences.
818+ * Only accepts string and number values for now and also sets 0-values.
819+ */
820+ function setResourceRequestAttributes (
821+ entry : Partial < PerformanceResourceTiming > & Partial < Record < ExperimentalResourceTimingProperty , number > > ,
780822 attributes : SpanAttributes ,
781- entry : PerformanceResourceTiming ,
782- key : keyof Pick < PerformanceResourceTiming , 'transferSize' | 'encodedBodySize' | 'decodedBodySize' > ,
783- dataKey : 'http.response_transfer_size' | 'http.response_content_length' | 'http.decoded_response_content_length' ,
823+ properties : [ keyof PerformanceResourceTiming | ExperimentalResourceTimingProperty , string ] [ ] ,
784824) : void {
785- const entryVal = entry [ key ] ;
786- if ( entryVal != null && entryVal < MAX_INT_AS_BYTES ) {
787- attributes [ dataKey ] = entryVal ;
788- }
825+ properties . forEach ( ( [ entryKey , attributeKey ] ) => {
826+ const entryVal = entry [ entryKey ] ;
827+ if (
828+ entryVal != null &&
829+ ( ( typeof entryVal === 'number' && entryVal < MAX_INT_AS_BYTES ) || typeof entryVal === 'string' )
830+ ) {
831+ attributes [ attributeKey ] = entryVal ;
832+ }
833+ } ) ;
789834}
790835
791836/**
0 commit comments