11import * as api from '@opentelemetry/api'
2- import { InstrumentationConfig , type Instrumentation } from '@opentelemetry/instrumentation'
3- import { _globalThis } from '@opentelemetry/core'
42import { SugaredTracer } from '@opentelemetry/api/experimental'
3+ import { _globalThis } from '@opentelemetry/core'
4+ import { InstrumentationConfig , type Instrumentation } from '@opentelemetry/instrumentation'
55
66export interface FetchInstrumentationConfig extends InstrumentationConfig {
7- getRequestAttributes ?( request : Request | RequestInit ) : api . Attributes
7+ getRequestAttributes ?( headers : Request ) : api . Attributes
88 getResponseAttributes ?( response : Response ) : api . Attributes
9- skipURLs ?: string [ ]
9+ skipURLs ?: ( string | RegExp ) [ ]
10+ skipHeaders ?: ( string | RegExp ) [ ] | true
11+ redactHeaders ?: ( string | RegExp ) [ ] | true
1012}
1113
1214export class FetchInstrumentation implements Instrumentation {
@@ -34,16 +36,6 @@ export class FetchInstrumentation implements Instrumentation {
3436 return this . provider
3537 }
3638
37- private annotateFromResponse ( span : api . Span , response : Response ) : void {
38- const extras = this . config . getResponseAttributes ?.( response ) ?? { }
39- // these are based on @opentelemetry /semantic-convention 1.36
40- span . setAttributes ( {
41- ...extras ,
42- 'http.response.status_code' : response . status ,
43- ...this . prepareHeaders ( 'response' , response . headers ) ,
44- } )
45- }
46-
4739 private annotateFromRequest ( span : api . Span , request : Request ) : void {
4840 const extras = this . config . getRequestAttributes ?.( request ) ?? { }
4941 const url = new URL ( request . url )
@@ -53,15 +45,50 @@ export class FetchInstrumentation implements Instrumentation {
5345 'http.request.method' : request . method ,
5446 'url.full' : url . href ,
5547 'url.host' : url . host ,
56- 'url.scheme' : url . protocol . replace ( ':' , '' ) ,
48+ 'url.scheme' : url . protocol . slice ( 0 , - 1 ) ,
5749 'server.address' : url . hostname ,
5850 'server.port' : url . port ,
5951 ...this . prepareHeaders ( 'request' , request . headers ) ,
6052 } )
6153 }
6254
55+ private annotateFromResponse ( span : api . Span , response : Response ) : void {
56+ const extras = this . config . getResponseAttributes ?.( response ) ?? { }
57+
58+ // these are based on @opentelemetry /semantic-convention 1.36
59+ span . setAttributes ( {
60+ ...extras ,
61+ 'http.response.status_code' : response . status ,
62+ ...this . prepareHeaders ( 'response' , response . headers ) ,
63+ } )
64+ }
65+
6366 private prepareHeaders ( type : 'request' | 'response' , headers : Headers ) : api . Attributes {
64- return Object . fromEntries ( Array . from ( headers . entries ( ) ) . map ( ( [ key , value ] ) => [ `${ type } .header.${ key } ` , value ] ) )
67+ if ( this . config . skipHeaders === true ) {
68+ return { }
69+ }
70+ const everything = [ '*' , '/.*/' ]
71+ const skips = this . config . skipHeaders ?? [ ]
72+ const redacts = this . config . redactHeaders ?? [ ]
73+ const everythingSkipped = skips . some ( ( skip ) => everything . includes ( skip . toString ( ) ) )
74+ const attributes : api . Attributes = { }
75+ if ( everythingSkipped ) return attributes
76+ const entries = headers . entries ( )
77+ for ( const [ key , value ] of entries ) {
78+ if ( skips . some ( ( skip ) => ( typeof skip == 'string' ? skip == key : skip . test ( key ) ) ) ) {
79+ continue
80+ }
81+ const attributeKey = `http.${ type } .header.${ key } `
82+ if (
83+ redacts === true ||
84+ redacts . some ( ( redact ) => ( typeof redact == 'string' ? redact == key : redact . test ( key ) ) )
85+ ) {
86+ attributes [ attributeKey ] = 'REDACTED'
87+ } else {
88+ attributes [ attributeKey ] = value
89+ }
90+ }
91+ return attributes
6592 }
6693
6794 private getTracer ( ) : SugaredTracer | undefined {
@@ -86,7 +113,10 @@ export class FetchInstrumentation implements Instrumentation {
86113 _globalThis . fetch = async ( resource : RequestInfo | URL , options ?: RequestInit ) : Promise < Response > => {
87114 const url = typeof resource === 'string' ? resource : resource instanceof URL ? resource . href : resource . url
88115 const tracer = this . getTracer ( )
89- if ( ! tracer || this . config . skipURLs ?. some ( ( skip ) => url . startsWith ( skip ) ) ) {
116+ if (
117+ ! tracer ||
118+ this . config . skipURLs ?. some ( ( skip ) => ( typeof skip == 'string' ? url . startsWith ( skip ) : skip . test ( url ) ) )
119+ ) {
90120 return await originalFetch ( resource , options )
91121 }
92122
0 commit comments