@opentelemetry/instrumentation-http 0.52.1 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,20 +61,56 @@ Http instrumentation has few options available to choose from. You can set the f
61
61
  | [`startOutgoingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L99) | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest |
62
62
  | `ignoreIncomingRequestHook` | `IgnoreIncomingRequestFunction` | Http instrumentation will not trace all incoming requests that matched with custom function |
63
63
  | `ignoreOutgoingRequestHook` | `IgnoreOutgoingRequestFunction` | Http instrumentation will not trace all outgoing requests that matched with custom function |
64
+ | `disableOutgoingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests. |
65
+ | `disableIncomingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests. |
64
66
  | [`serverName`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L101) | `string` | The primary server name of the matched virtual host. |
65
67
  | [`requireParentforOutgoingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L103) | Boolean | Require that is a parent span to create new span for outgoing requests. |
66
68
  | [`requireParentforIncomingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L105) | Boolean | Require that is a parent span to create new span for incoming requests. |
67
69
  | [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L107) | `object` | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
68
70
 
69
- The following options are deprecated:
71
+ ## Semantic Conventions
70
72
 
71
- | Options | Type | Description |
72
- | ------- | ---- | ----------- |
73
- | `ignoreIncomingPaths` | `IgnoreMatcher[]` | Http instrumentation will not trace all incoming requests that match paths |
73
+ Prior to version `0.54`, this instrumentation created spans targeting an experimental semantic convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md).
74
74
 
75
- ## Semantic Conventions
75
+ This package is capable of emitting both Semantic Convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md) and [Version 1.27.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md).
76
+ It is controlled using the environment variable `OTEL_SEMCONV_STABILITY_OPT_IN`, which is a comma separated list of values.
77
+ The values `http` and `http/dup` control this instrumentation.
78
+ See details for the behavior of each of these values below.
79
+ If neither `http` or `http/dup` is included in `OTEL_SEMCONV_STABILITY_OPT_IN`, the old experimental semantic conventions will be used by default.
80
+
81
+ ### Stable Semantic Conventions 1.27
82
+
83
+ Enabled when `OTEL_SEMCONV_STABILITY_OPT_IN` contains `http` OR `http/dup`.
84
+ This is the recommended configuration, and will soon become the default behavior.
85
+
86
+ Follow all requirements and recommendations of HTTP Client and Server Semantic Conventions Version 1.27.0 for [spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md) and [metrics](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md), including all required and recommended attributes.
87
+
88
+ Metrics Exported:
89
+
90
+ - [`http.server.request.duration`](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md#metric-httpserverrequestduration)
91
+ - [`http.client.request.duration`](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md#metric-httpclientrequestduration)
92
+
93
+ ### Upgrading Semantic Conventions
94
+
95
+ When upgrading to the new semantic conventions, it is recommended to do so in the following order:
96
+
97
+ 1. Upgrade `@opentelemetry/instrumentation-http` to the latest version
98
+ 2. Set `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` to emit both old and new semantic conventions
99
+ 3. Modify alerts, dashboards, metrics, and other processes to expect the new semantic conventions
100
+ 4. Set `OTEL_SEMCONV_STABILITY_OPT_IN=http` to emit only the new semantic conventions
101
+
102
+ This will cause both the old and new semantic conventions to be emitted during the transition period.
103
+
104
+ ### Legacy Behavior (default)
105
+
106
+ Enabled when `OTEL_SEMCONV_STABILITY_OPT_IN` contains `http/dup` or DOES NOT CONTAIN `http`.
107
+ This is the current default behavior.
108
+
109
+ Create HTTP client spans which implement Semantic Convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md).
110
+
111
+ #### Server Spans (legacy)
76
112
 
77
- This package uses `@opentelemetry/semantic-conventions` version `1.22+`, which implements Semantic Convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md)
113
+ When `OTEL_SEMCONV_STABILITY_OPT_IN` is not set or includes `http/dup`, this module implements Semantic Convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md).
78
114
 
79
115
  Attributes collected:
80
116
 
@@ -4,16 +4,21 @@ import * as url from 'url';
4
4
  import { Func, HttpInstrumentationConfig, HttpRequestArgs } from './types';
5
5
  import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
6
6
  /**
7
- * Http instrumentation instrumentation for Opentelemetry
7
+ * `node:http` and `node:https` instrumentation for OpenTelemetry
8
8
  */
9
9
  export declare class HttpInstrumentation extends InstrumentationBase<HttpInstrumentationConfig> {
10
10
  /** keep track on spans not ended */
11
11
  private readonly _spanNotEnded;
12
12
  private _headerCapture;
13
- private _httpServerDurationHistogram;
14
- private _httpClientDurationHistogram;
13
+ private _oldHttpServerDurationHistogram;
14
+ private _stableHttpServerDurationHistogram;
15
+ private _oldHttpClientDurationHistogram;
16
+ private _stableHttpClientDurationHistogram;
17
+ private _semconvStability;
15
18
  constructor(config?: HttpInstrumentationConfig);
16
19
  protected _updateMetricInstruments(): void;
20
+ private _recordServerDuration;
21
+ private _recordClientDuration;
17
22
  setConfig(config?: HttpInstrumentationConfig): void;
18
23
  init(): [
19
24
  InstrumentationNodeModuleDefinition,
@@ -42,7 +47,8 @@ export declare class HttpInstrumentation extends InstrumentationBase<HttpInstrum
42
47
  * @param request The original request object.
43
48
  * @param span representing the current operation
44
49
  * @param startTime representing the start time of the request to calculate duration in Metric
45
- * @param metricAttributes metric attributes
50
+ * @param oldMetricAttributes metric attributes for old semantic conventions
51
+ * @param stableMetricAttributes metric attributes for new semantic conventions
46
52
  */
47
53
  private _traceClientRequest;
48
54
  private _incomingRequestFunction;
package/build/src/http.js CHANGED
@@ -20,33 +20,90 @@ const api_1 = require("@opentelemetry/api");
20
20
  const core_1 = require("@opentelemetry/core");
21
21
  const semver = require("semver");
22
22
  const url = require("url");
23
- const utils = require("./utils");
24
23
  const version_1 = require("./version");
25
24
  const instrumentation_1 = require("@opentelemetry/instrumentation");
26
25
  const core_2 = require("@opentelemetry/core");
27
26
  const events_1 = require("events");
28
27
  const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
28
+ const utils_1 = require("./utils");
29
29
  /**
30
- * Http instrumentation instrumentation for Opentelemetry
30
+ * `node:http` and `node:https` instrumentation for OpenTelemetry
31
31
  */
32
32
  class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
33
33
  constructor(config = {}) {
34
34
  super('@opentelemetry/instrumentation-http', version_1.VERSION, config);
35
35
  /** keep track on spans not ended */
36
36
  this._spanNotEnded = new WeakSet();
37
+ this._semconvStability = 2 /* OLD */;
37
38
  this._headerCapture = this._createHeaderCapture();
39
+ for (const entry in (0, core_2.getEnv)().OTEL_SEMCONV_STABILITY_OPT_IN) {
40
+ if (entry.toLowerCase() === 'http/dup') {
41
+ // http/dup takes highest precedence. If it is found, there is no need to read the rest of the list
42
+ this._semconvStability = 3 /* DUPLICATE */;
43
+ break;
44
+ }
45
+ else if (entry.toLowerCase() === 'http') {
46
+ this._semconvStability = 1 /* STABLE */;
47
+ }
48
+ }
38
49
  }
39
50
  _updateMetricInstruments() {
40
- this._httpServerDurationHistogram = this.meter.createHistogram('http.server.duration', {
51
+ this._oldHttpServerDurationHistogram = this.meter.createHistogram('http.server.duration', {
41
52
  description: 'Measures the duration of inbound HTTP requests.',
42
53
  unit: 'ms',
43
54
  valueType: api_1.ValueType.DOUBLE,
44
55
  });
45
- this._httpClientDurationHistogram = this.meter.createHistogram('http.client.duration', {
56
+ this._oldHttpClientDurationHistogram = this.meter.createHistogram('http.client.duration', {
46
57
  description: 'Measures the duration of outbound HTTP requests.',
47
58
  unit: 'ms',
48
59
  valueType: api_1.ValueType.DOUBLE,
49
60
  });
61
+ this._stableHttpServerDurationHistogram = this.meter.createHistogram(semantic_conventions_1.METRIC_HTTP_SERVER_REQUEST_DURATION, {
62
+ description: 'Duration of HTTP server requests.',
63
+ unit: 's',
64
+ valueType: api_1.ValueType.DOUBLE,
65
+ advice: {
66
+ explicitBucketBoundaries: [
67
+ 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5,
68
+ 7.5, 10,
69
+ ],
70
+ },
71
+ });
72
+ this._stableHttpClientDurationHistogram = this.meter.createHistogram(semantic_conventions_1.METRIC_HTTP_CLIENT_REQUEST_DURATION, {
73
+ description: 'Duration of HTTP client requests.',
74
+ unit: 's',
75
+ valueType: api_1.ValueType.DOUBLE,
76
+ advice: {
77
+ explicitBucketBoundaries: [
78
+ 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5,
79
+ 7.5, 10,
80
+ ],
81
+ },
82
+ });
83
+ }
84
+ _recordServerDuration(durationMs, oldAttributes, stableAttributes) {
85
+ if ((this._semconvStability & 2 /* OLD */) ===
86
+ 2 /* OLD */) {
87
+ // old histogram is counted in MS
88
+ this._oldHttpServerDurationHistogram.record(durationMs, oldAttributes);
89
+ }
90
+ if ((this._semconvStability & 1 /* STABLE */) ===
91
+ 1 /* STABLE */) {
92
+ // stable histogram is counted in S
93
+ this._stableHttpServerDurationHistogram.record(durationMs / 1000, stableAttributes);
94
+ }
95
+ }
96
+ _recordClientDuration(durationMs, oldAttributes, stableAttributes) {
97
+ if ((this._semconvStability & 2 /* OLD */) ===
98
+ 2 /* OLD */) {
99
+ // old histogram is counted in MS
100
+ this._oldHttpClientDurationHistogram.record(durationMs, oldAttributes);
101
+ }
102
+ if ((this._semconvStability & 1 /* STABLE */) ===
103
+ 1 /* STABLE */) {
104
+ // stable histogram is counted in S
105
+ this._stableHttpClientDurationHistogram.record(durationMs / 1000, stableAttributes);
106
+ }
50
107
  }
51
108
  setConfig(config = {}) {
52
109
  super.setConfig(config);
@@ -57,30 +114,60 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
57
114
  }
58
115
  _getHttpInstrumentation() {
59
116
  return new instrumentation_1.InstrumentationNodeModuleDefinition('http', ['*'], (moduleExports) => {
60
- this._wrap(moduleExports, 'request', this._getPatchOutgoingRequestFunction('http'));
61
- this._wrap(moduleExports, 'get', this._getPatchOutgoingGetFunction(moduleExports.request));
62
- this._wrap(moduleExports.Server.prototype, 'emit', this._getPatchIncomingRequestFunction('http'));
117
+ const isESM = moduleExports[Symbol.toStringTag] === 'Module';
118
+ if (!this.getConfig().disableOutgoingRequestInstrumentation) {
119
+ const patchedRequest = this._wrap(moduleExports, 'request', this._getPatchOutgoingRequestFunction('http'));
120
+ const patchedGet = this._wrap(moduleExports, 'get', this._getPatchOutgoingGetFunction(patchedRequest));
121
+ if (isESM) {
122
+ // To handle `import http from 'http'`, which returns the default
123
+ // export, we need to set `module.default.*`.
124
+ moduleExports.default.request = patchedRequest;
125
+ moduleExports.default.get = patchedGet;
126
+ }
127
+ }
128
+ if (!this.getConfig().disableIncomingRequestInstrumentation) {
129
+ this._wrap(moduleExports.Server.prototype, 'emit', this._getPatchIncomingRequestFunction('http'));
130
+ }
63
131
  return moduleExports;
64
132
  }, (moduleExports) => {
65
133
  if (moduleExports === undefined)
66
134
  return;
67
- this._unwrap(moduleExports, 'request');
68
- this._unwrap(moduleExports, 'get');
69
- this._unwrap(moduleExports.Server.prototype, 'emit');
135
+ if (!this.getConfig().disableOutgoingRequestInstrumentation) {
136
+ this._unwrap(moduleExports, 'request');
137
+ this._unwrap(moduleExports, 'get');
138
+ }
139
+ if (!this.getConfig().disableIncomingRequestInstrumentation) {
140
+ this._unwrap(moduleExports.Server.prototype, 'emit');
141
+ }
70
142
  });
71
143
  }
72
144
  _getHttpsInstrumentation() {
73
145
  return new instrumentation_1.InstrumentationNodeModuleDefinition('https', ['*'], (moduleExports) => {
74
- this._wrap(moduleExports, 'request', this._getPatchHttpsOutgoingRequestFunction('https'));
75
- this._wrap(moduleExports, 'get', this._getPatchHttpsOutgoingGetFunction(moduleExports.request));
76
- this._wrap(moduleExports.Server.prototype, 'emit', this._getPatchIncomingRequestFunction('https'));
146
+ const isESM = moduleExports[Symbol.toStringTag] === 'Module';
147
+ if (!this.getConfig().disableOutgoingRequestInstrumentation) {
148
+ const patchedRequest = this._wrap(moduleExports, 'request', this._getPatchHttpsOutgoingRequestFunction('https'));
149
+ const patchedGet = this._wrap(moduleExports, 'get', this._getPatchHttpsOutgoingGetFunction(patchedRequest));
150
+ if (isESM) {
151
+ // To handle `import https from 'https'`, which returns the default
152
+ // export, we need to set `module.default.*`.
153
+ moduleExports.default.request = patchedRequest;
154
+ moduleExports.default.get = patchedGet;
155
+ }
156
+ }
157
+ if (!this.getConfig().disableIncomingRequestInstrumentation) {
158
+ this._wrap(moduleExports.Server.prototype, 'emit', this._getPatchIncomingRequestFunction('https'));
159
+ }
77
160
  return moduleExports;
78
161
  }, (moduleExports) => {
79
162
  if (moduleExports === undefined)
80
163
  return;
81
- this._unwrap(moduleExports, 'request');
82
- this._unwrap(moduleExports, 'get');
83
- this._unwrap(moduleExports.Server.prototype, 'emit');
164
+ if (!this.getConfig().disableOutgoingRequestInstrumentation) {
165
+ this._unwrap(moduleExports, 'request');
166
+ this._unwrap(moduleExports, 'get');
167
+ }
168
+ if (!this.getConfig().disableIncomingRequestInstrumentation) {
169
+ this._unwrap(moduleExports.Server.prototype, 'emit');
170
+ }
84
171
  });
85
172
  }
86
173
  /**
@@ -159,9 +246,10 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
159
246
  * @param request The original request object.
160
247
  * @param span representing the current operation
161
248
  * @param startTime representing the start time of the request to calculate duration in Metric
162
- * @param metricAttributes metric attributes
249
+ * @param oldMetricAttributes metric attributes for old semantic conventions
250
+ * @param stableMetricAttributes metric attributes for new semantic conventions
163
251
  */
164
- _traceClientRequest(request, span, startTime, metricAttributes) {
252
+ _traceClientRequest(request, span, startTime, oldMetricAttributes, stableMetricAttributes) {
165
253
  if (this.getConfig().requestHook) {
166
254
  this._callRequestHook(span, request);
167
255
  }
@@ -179,9 +267,9 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
179
267
  if (request.listenerCount('response') <= 1) {
180
268
  response.resume();
181
269
  }
182
- const responseAttributes = utils.getOutgoingRequestAttributesOnResponse(response);
270
+ const responseAttributes = (0, utils_1.getOutgoingRequestAttributesOnResponse)(response, this._semconvStability);
183
271
  span.setAttributes(responseAttributes);
184
- metricAttributes = Object.assign(metricAttributes, utils.getOutgoingRequestMetricAttributesOnResponse(responseAttributes));
272
+ oldMetricAttributes = Object.assign(oldMetricAttributes, (0, utils_1.getOutgoingRequestMetricAttributesOnResponse)(responseAttributes));
185
273
  if (this.getConfig().responseHook) {
186
274
  this._callResponseHook(span, response);
187
275
  }
@@ -199,15 +287,16 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
199
287
  status = { code: api_1.SpanStatusCode.ERROR };
200
288
  }
201
289
  else {
290
+ // behaves same for new and old semconv
202
291
  status = {
203
- code: utils.parseResponseStatus(api_1.SpanKind.CLIENT, response.statusCode),
292
+ code: (0, utils_1.parseResponseStatus)(api_1.SpanKind.CLIENT, response.statusCode),
204
293
  };
205
294
  }
206
295
  span.setStatus(status);
207
296
  if (this.getConfig().applyCustomAttributesOnSpan) {
208
297
  (0, instrumentation_1.safeExecuteInTheMiddle)(() => this.getConfig().applyCustomAttributesOnSpan(span, request, response), () => { }, true);
209
298
  }
210
- this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, metricAttributes);
299
+ this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
211
300
  };
212
301
  response.on('end', endHandler);
213
302
  // See https://github.com/open-telemetry/opentelemetry-js/pull/3625#issuecomment-1475673533
@@ -220,12 +309,12 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
220
309
  return;
221
310
  }
222
311
  responseFinished = true;
223
- utils.setSpanWithError(span, error);
312
+ (0, utils_1.setSpanWithError)(span, error, this._semconvStability);
224
313
  span.setStatus({
225
314
  code: api_1.SpanStatusCode.ERROR,
226
315
  message: error.message,
227
316
  });
228
- this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, metricAttributes);
317
+ this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
229
318
  });
230
319
  });
231
320
  request.on('close', () => {
@@ -234,7 +323,7 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
234
323
  return;
235
324
  }
236
325
  responseFinished = true;
237
- this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, metricAttributes);
326
+ this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
238
327
  });
239
328
  request.on(events_1.errorMonitor, (error) => {
240
329
  this._diag.debug('outgoingRequest on request error()', error);
@@ -242,8 +331,8 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
242
331
  return;
243
332
  }
244
333
  responseFinished = true;
245
- utils.setSpanWithError(span, error);
246
- this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, metricAttributes);
334
+ (0, utils_1.setSpanWithError)(span, error, this._semconvStability);
335
+ this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
247
336
  });
248
337
  this._diag.debug('http.ClientRequest return request');
249
338
  return request;
@@ -257,17 +346,13 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
257
346
  }
258
347
  const request = args[0];
259
348
  const response = args[1];
260
- const pathname = request.url
261
- ? url.parse(request.url).pathname || '/'
262
- : '/';
263
349
  const method = request.method || 'GET';
264
350
  instrumentation._diag.debug(`${component} instrumentation incomingRequest`);
265
- if (utils.isIgnored(pathname, instrumentation.getConfig().ignoreIncomingPaths, (e) => instrumentation._diag.error('caught ignoreIncomingPaths error: ', e)) ||
266
- (0, instrumentation_1.safeExecuteInTheMiddle)(() => { var _a, _b; return (_b = (_a = instrumentation.getConfig()).ignoreIncomingRequestHook) === null || _b === void 0 ? void 0 : _b.call(_a, request); }, (e) => {
267
- if (e != null) {
268
- instrumentation._diag.error('caught ignoreIncomingRequestHook error: ', e);
269
- }
270
- }, true)) {
351
+ if ((0, instrumentation_1.safeExecuteInTheMiddle)(() => { var _a, _b; return (_b = (_a = instrumentation.getConfig()).ignoreIncomingRequestHook) === null || _b === void 0 ? void 0 : _b.call(_a, request); }, (e) => {
352
+ if (e != null) {
353
+ instrumentation._diag.error('caught ignoreIncomingRequestHook error: ', e);
354
+ }
355
+ }, true)) {
271
356
  return api_1.context.with((0, core_1.suppressTracing)(api_1.context.active()), () => {
272
357
  api_1.context.bind(api_1.context.active(), request);
273
358
  api_1.context.bind(api_1.context.active(), response);
@@ -275,17 +360,28 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
275
360
  });
276
361
  }
277
362
  const headers = request.headers;
278
- const spanAttributes = utils.getIncomingRequestAttributes(request, {
363
+ const spanAttributes = (0, utils_1.getIncomingRequestAttributes)(request, {
279
364
  component: component,
280
365
  serverName: instrumentation.getConfig().serverName,
281
366
  hookAttributes: instrumentation._callStartSpanHook(request, instrumentation.getConfig().startIncomingSpanHook),
367
+ semconvStability: instrumentation._semconvStability,
282
368
  });
283
369
  const spanOptions = {
284
370
  kind: api_1.SpanKind.SERVER,
285
371
  attributes: spanAttributes,
286
372
  };
287
373
  const startTime = (0, core_1.hrTime)();
288
- const metricAttributes = utils.getIncomingRequestMetricAttributes(spanAttributes);
374
+ const oldMetricAttributes = (0, utils_1.getIncomingRequestMetricAttributes)(spanAttributes);
375
+ // request method and url.scheme are both required span attributes
376
+ const stableMetricAttributes = {
377
+ [semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD]: spanAttributes[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD],
378
+ [semantic_conventions_1.ATTR_URL_SCHEME]: spanAttributes[semantic_conventions_1.ATTR_URL_SCHEME],
379
+ };
380
+ // recommended if and only if one was sent, same as span recommendation
381
+ if (spanAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION]) {
382
+ stableMetricAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION] =
383
+ spanAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION];
384
+ }
289
385
  const ctx = api_1.propagation.extract(api_1.ROOT_CONTEXT, headers);
290
386
  const span = instrumentation._startHttpSpan(method, spanOptions, ctx);
291
387
  const rpcMetadata = {
@@ -308,16 +404,16 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
308
404
  if (hasError) {
309
405
  return;
310
406
  }
311
- instrumentation._onServerResponseFinish(request, response, span, metricAttributes, startTime);
407
+ instrumentation._onServerResponseFinish(request, response, span, oldMetricAttributes, stableMetricAttributes, startTime);
312
408
  });
313
409
  response.on(events_1.errorMonitor, (err) => {
314
410
  hasError = true;
315
- instrumentation._onServerResponseError(span, metricAttributes, startTime, err);
411
+ instrumentation._onServerResponseError(span, oldMetricAttributes, stableMetricAttributes, startTime, err);
316
412
  });
317
413
  return (0, instrumentation_1.safeExecuteInTheMiddle)(() => original.apply(this, [event, ...args]), error => {
318
414
  if (error) {
319
- utils.setSpanWithError(span, error);
320
- instrumentation._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, metricAttributes);
415
+ (0, utils_1.setSpanWithError)(span, error, instrumentation._semconvStability);
416
+ instrumentation._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, oldMetricAttributes, stableMetricAttributes);
321
417
  throw error;
322
418
  }
323
419
  });
@@ -327,14 +423,14 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
327
423
  _outgoingRequestFunction(component, original) {
328
424
  const instrumentation = this;
329
425
  return function outgoingRequest(options, ...args) {
330
- if (!utils.isValidOptionsType(options)) {
426
+ if (!(0, utils_1.isValidOptionsType)(options)) {
331
427
  return original.apply(this, [options, ...args]);
332
428
  }
333
429
  const extraOptions = typeof args[0] === 'object' &&
334
430
  (typeof options === 'string' || options instanceof url.URL)
335
431
  ? args.shift()
336
432
  : undefined;
337
- const { origin, pathname, method, optionsParsed } = utils.getRequestInfo(options, extraOptions);
433
+ const { method, optionsParsed } = (0, utils_1.getRequestInfo)(options, extraOptions);
338
434
  /**
339
435
  * Node 8's https module directly call the http one so to avoid creating
340
436
  * 2 span for the same request we need to check that the protocol is correct
@@ -345,27 +441,42 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
345
441
  optionsParsed.protocol === 'https:') {
346
442
  return original.apply(this, [optionsParsed, ...args]);
347
443
  }
348
- if (utils.isIgnored(origin + pathname, instrumentation.getConfig().ignoreOutgoingUrls, (e) => instrumentation._diag.error('caught ignoreOutgoingUrls error: ', e)) ||
349
- (0, instrumentation_1.safeExecuteInTheMiddle)(() => {
350
- var _a, _b;
351
- return (_b = (_a = instrumentation
352
- .getConfig()).ignoreOutgoingRequestHook) === null || _b === void 0 ? void 0 : _b.call(_a, optionsParsed);
353
- }, (e) => {
354
- if (e != null) {
355
- instrumentation._diag.error('caught ignoreOutgoingRequestHook error: ', e);
356
- }
357
- }, true)) {
444
+ if ((0, instrumentation_1.safeExecuteInTheMiddle)(() => {
445
+ var _a, _b;
446
+ return (_b = (_a = instrumentation
447
+ .getConfig()).ignoreOutgoingRequestHook) === null || _b === void 0 ? void 0 : _b.call(_a, optionsParsed);
448
+ }, (e) => {
449
+ if (e != null) {
450
+ instrumentation._diag.error('caught ignoreOutgoingRequestHook error: ', e);
451
+ }
452
+ }, true)) {
358
453
  return original.apply(this, [optionsParsed, ...args]);
359
454
  }
360
- const { hostname, port } = utils.extractHostnameAndPort(optionsParsed);
361
- const attributes = utils.getOutgoingRequestAttributes(optionsParsed, {
455
+ const { hostname, port } = (0, utils_1.extractHostnameAndPort)(optionsParsed);
456
+ const attributes = (0, utils_1.getOutgoingRequestAttributes)(optionsParsed, {
362
457
  component,
363
458
  port,
364
459
  hostname,
365
460
  hookAttributes: instrumentation._callStartSpanHook(optionsParsed, instrumentation.getConfig().startOutgoingSpanHook),
366
- });
461
+ }, instrumentation._semconvStability);
367
462
  const startTime = (0, core_1.hrTime)();
368
- const metricAttributes = utils.getOutgoingRequestMetricAttributes(attributes);
463
+ const oldMetricAttributes = (0, utils_1.getOutgoingRequestMetricAttributes)(attributes);
464
+ // request method, server address, and server port are both required span attributes
465
+ const stableMetricAttributes = {
466
+ [semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD]: attributes[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD],
467
+ [semantic_conventions_1.ATTR_SERVER_ADDRESS]: attributes[semantic_conventions_1.ATTR_SERVER_ADDRESS],
468
+ [semantic_conventions_1.ATTR_SERVER_PORT]: attributes[semantic_conventions_1.ATTR_SERVER_PORT],
469
+ };
470
+ // required if and only if one was sent, same as span requirement
471
+ if (attributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE]) {
472
+ stableMetricAttributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE] =
473
+ attributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE];
474
+ }
475
+ // recommended if and only if one was sent, same as span recommendation
476
+ if (attributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION]) {
477
+ stableMetricAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION] =
478
+ attributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION];
479
+ }
369
480
  const spanOptions = {
370
481
  kind: api_1.SpanKind.CLIENT,
371
482
  attributes,
@@ -393,23 +504,24 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
393
504
  }
394
505
  const request = (0, instrumentation_1.safeExecuteInTheMiddle)(() => original.apply(this, [optionsParsed, ...args]), error => {
395
506
  if (error) {
396
- utils.setSpanWithError(span, error);
397
- instrumentation._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, metricAttributes);
507
+ (0, utils_1.setSpanWithError)(span, error, instrumentation._semconvStability);
508
+ instrumentation._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
398
509
  throw error;
399
510
  }
400
511
  });
401
512
  instrumentation._diag.debug(`${component} instrumentation outgoingRequest`);
402
513
  api_1.context.bind(parentContext, request);
403
- return instrumentation._traceClientRequest(request, span, startTime, metricAttributes);
514
+ return instrumentation._traceClientRequest(request, span, startTime, oldMetricAttributes, stableMetricAttributes);
404
515
  });
405
516
  };
406
517
  }
407
- _onServerResponseFinish(request, response, span, metricAttributes, startTime) {
408
- const attributes = utils.getIncomingRequestAttributesOnResponse(request, response);
409
- metricAttributes = Object.assign(metricAttributes, utils.getIncomingRequestMetricAttributesOnResponse(attributes));
518
+ _onServerResponseFinish(request, response, span, oldMetricAttributes, stableMetricAttributes, startTime) {
519
+ const attributes = (0, utils_1.getIncomingRequestAttributesOnResponse)(request, response, this._semconvStability);
520
+ oldMetricAttributes = Object.assign(oldMetricAttributes, (0, utils_1.getIncomingRequestMetricAttributesOnResponse)(attributes));
521
+ stableMetricAttributes = Object.assign(stableMetricAttributes, (0, utils_1.getIncomingStableRequestMetricAttributesOnResponse)(attributes));
410
522
  this._headerCapture.server.captureResponseHeaders(span, header => response.getHeader(header));
411
523
  span.setAttributes(attributes).setStatus({
412
- code: utils.parseResponseStatus(api_1.SpanKind.SERVER, response.statusCode),
524
+ code: (0, utils_1.parseResponseStatus)(api_1.SpanKind.SERVER, response.statusCode),
413
525
  });
414
526
  const route = attributes[semantic_conventions_1.SEMATTRS_HTTP_ROUTE];
415
527
  if (route) {
@@ -418,11 +530,12 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
418
530
  if (this.getConfig().applyCustomAttributesOnSpan) {
419
531
  (0, instrumentation_1.safeExecuteInTheMiddle)(() => this.getConfig().applyCustomAttributesOnSpan(span, request, response), () => { }, true);
420
532
  }
421
- this._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, metricAttributes);
533
+ this._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, oldMetricAttributes, stableMetricAttributes);
422
534
  }
423
- _onServerResponseError(span, metricAttributes, startTime, error) {
424
- utils.setSpanWithError(span, error);
425
- this._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, metricAttributes);
535
+ _onServerResponseError(span, oldMetricAttributes, stableMetricAttributes, startTime, error) {
536
+ (0, utils_1.setSpanWithError)(span, error, this._semconvStability);
537
+ // TODO get error attributes for metrics
538
+ this._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, oldMetricAttributes, stableMetricAttributes);
426
539
  }
427
540
  _startHttpSpan(name, options, ctx = api_1.context.active()) {
428
541
  /*
@@ -446,7 +559,7 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
446
559
  this._spanNotEnded.add(span);
447
560
  return span;
448
561
  }
449
- _closeHttpSpan(span, spanKind, startTime, metricAttributes) {
562
+ _closeHttpSpan(span, spanKind, startTime, oldMetricAttributes, stableMetricAttributes) {
450
563
  if (!this._spanNotEnded.has(span)) {
451
564
  return;
452
565
  }
@@ -455,10 +568,10 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
455
568
  // Record metrics
456
569
  const duration = (0, core_1.hrTimeToMilliseconds)((0, core_1.hrTimeDuration)(startTime, (0, core_1.hrTime)()));
457
570
  if (spanKind === api_1.SpanKind.SERVER) {
458
- this._httpServerDurationHistogram.record(duration, metricAttributes);
571
+ this._recordServerDuration(duration, oldMetricAttributes, stableMetricAttributes);
459
572
  }
460
573
  else if (spanKind === api_1.SpanKind.CLIENT) {
461
- this._httpClientDurationHistogram.record(duration, metricAttributes);
574
+ this._recordClientDuration(duration, oldMetricAttributes, stableMetricAttributes);
462
575
  }
463
576
  }
464
577
  _callResponseHook(span, response) {
@@ -477,12 +590,12 @@ class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
477
590
  const config = this.getConfig();
478
591
  return {
479
592
  client: {
480
- captureRequestHeaders: utils.headerCapture('request', (_c = (_b = (_a = config.headersToSpanAttributes) === null || _a === void 0 ? void 0 : _a.client) === null || _b === void 0 ? void 0 : _b.requestHeaders) !== null && _c !== void 0 ? _c : []),
481
- captureResponseHeaders: utils.headerCapture('response', (_f = (_e = (_d = config.headersToSpanAttributes) === null || _d === void 0 ? void 0 : _d.client) === null || _e === void 0 ? void 0 : _e.responseHeaders) !== null && _f !== void 0 ? _f : []),
593
+ captureRequestHeaders: (0, utils_1.headerCapture)('request', (_c = (_b = (_a = config.headersToSpanAttributes) === null || _a === void 0 ? void 0 : _a.client) === null || _b === void 0 ? void 0 : _b.requestHeaders) !== null && _c !== void 0 ? _c : []),
594
+ captureResponseHeaders: (0, utils_1.headerCapture)('response', (_f = (_e = (_d = config.headersToSpanAttributes) === null || _d === void 0 ? void 0 : _d.client) === null || _e === void 0 ? void 0 : _e.responseHeaders) !== null && _f !== void 0 ? _f : []),
482
595
  },
483
596
  server: {
484
- captureRequestHeaders: utils.headerCapture('request', (_j = (_h = (_g = config.headersToSpanAttributes) === null || _g === void 0 ? void 0 : _g.server) === null || _h === void 0 ? void 0 : _h.requestHeaders) !== null && _j !== void 0 ? _j : []),
485
- captureResponseHeaders: utils.headerCapture('response', (_m = (_l = (_k = config.headersToSpanAttributes) === null || _k === void 0 ? void 0 : _k.server) === null || _l === void 0 ? void 0 : _l.responseHeaders) !== null && _m !== void 0 ? _m : []),
597
+ captureRequestHeaders: (0, utils_1.headerCapture)('request', (_j = (_h = (_g = config.headersToSpanAttributes) === null || _g === void 0 ? void 0 : _g.server) === null || _h === void 0 ? void 0 : _h.requestHeaders) !== null && _j !== void 0 ? _j : []),
598
+ captureResponseHeaders: (0, utils_1.headerCapture)('response', (_m = (_l = (_k = config.headersToSpanAttributes) === null || _k === void 0 ? void 0 : _k.server) === null || _l === void 0 ? void 0 : _l.responseHeaders) !== null && _m !== void 0 ? _m : []),
486
599
  },
487
600
  };
488
601
  }