1
1
import * as api from '@opentelemetry/api'
2
- import { InstrumentationConfig , type Instrumentation } from '@opentelemetry/instrumentation'
3
- import { _globalThis } from '@opentelemetry/core'
4
2
import { SugaredTracer } from '@opentelemetry/api/experimental'
3
+ import { _globalThis } from '@opentelemetry/core'
4
+ import { InstrumentationConfig , type Instrumentation } from '@opentelemetry/instrumentation'
5
5
6
6
export interface FetchInstrumentationConfig extends InstrumentationConfig {
7
- getRequestAttributes ?( request : Request | RequestInit ) : api . Attributes
7
+ getRequestAttributes ?( headers : Request ) : api . Attributes
8
8
getResponseAttributes ?( response : Response ) : api . Attributes
9
- skipURLs ?: string [ ]
9
+ skipURLs ?: ( string | RegExp ) [ ]
10
+ skipHeaders ?: ( string | RegExp ) [ ] | true
11
+ redactHeaders ?: ( string | RegExp ) [ ] | true
10
12
}
11
13
12
14
export class FetchInstrumentation implements Instrumentation {
@@ -34,16 +36,6 @@ export class FetchInstrumentation implements Instrumentation {
34
36
return this . provider
35
37
}
36
38
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
-
47
39
private annotateFromRequest ( span : api . Span , request : Request ) : void {
48
40
const extras = this . config . getRequestAttributes ?.( request ) ?? { }
49
41
const url = new URL ( request . url )
@@ -53,15 +45,50 @@ export class FetchInstrumentation implements Instrumentation {
53
45
'http.request.method' : request . method ,
54
46
'url.full' : url . href ,
55
47
'url.host' : url . host ,
56
- 'url.scheme' : url . protocol . replace ( ':' , '' ) ,
48
+ 'url.scheme' : url . protocol . slice ( 0 , - 1 ) ,
57
49
'server.address' : url . hostname ,
58
50
'server.port' : url . port ,
59
51
...this . prepareHeaders ( 'request' , request . headers ) ,
60
52
} )
61
53
}
62
54
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
+
63
66
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
65
92
}
66
93
67
94
private getTracer ( ) : SugaredTracer | undefined {
@@ -86,7 +113,10 @@ export class FetchInstrumentation implements Instrumentation {
86
113
_globalThis . fetch = async ( resource : RequestInfo | URL , options ?: RequestInit ) : Promise < Response > => {
87
114
const url = typeof resource === 'string' ? resource : resource instanceof URL ? resource . href : resource . url
88
115
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
+ ) {
90
120
return await originalFetch ( resource , options )
91
121
}
92
122
0 commit comments