@openapi-typescript-infra/service 2.9.1 → 2.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openapi-typescript-infra/service",
3
- "version": "2.9.1",
3
+ "version": "2.10.0",
4
4
  "description": "An opinionated framework for building configuration driven services - web, api, or ob. Uses OpenAPI, pino logging, express, confit, Typescript and vitest.",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
@@ -65,23 +65,23 @@
65
65
  "@opentelemetry/exporter-prometheus": "^0.43.0",
66
66
  "@opentelemetry/exporter-trace-otlp-proto": "^0.43.0",
67
67
  "@opentelemetry/instrumentation": "^0.43.0",
68
- "@opentelemetry/instrumentation-dns": "^0.32.2",
69
- "@opentelemetry/instrumentation-express": "^0.33.1",
70
- "@opentelemetry/instrumentation-fetch": "^0.43.0",
71
- "@opentelemetry/instrumentation-generic-pool": "^0.32.2",
72
- "@opentelemetry/instrumentation-graphql": "^0.35.1",
68
+ "@opentelemetry/instrumentation-dns": "^0.32.3",
69
+ "@opentelemetry/instrumentation-express": "^0.33.2",
70
+ "@opentelemetry/instrumentation-generic-pool": "^0.32.3",
71
+ "@opentelemetry/instrumentation-graphql": "^0.35.2",
73
72
  "@opentelemetry/instrumentation-http": "^0.43.0",
74
- "@opentelemetry/instrumentation-ioredis": "^0.35.1",
75
- "@opentelemetry/instrumentation-net": "^0.32.1",
76
- "@opentelemetry/instrumentation-pg": "^0.36.1",
77
- "@opentelemetry/instrumentation-pino": "^0.34.1",
78
- "@opentelemetry/resource-detector-container": "^0.3.1",
79
- "@opentelemetry/resource-detector-gcp": "^0.29.1",
80
- "@opentelemetry/resources": "^1.17.0",
81
- "@opentelemetry/sdk-metrics": "^1.17.0",
73
+ "@opentelemetry/instrumentation-ioredis": "^0.35.2",
74
+ "@opentelemetry/instrumentation-net": "^0.32.2",
75
+ "@opentelemetry/instrumentation-pg": "^0.36.2",
76
+ "@opentelemetry/instrumentation-pino": "^0.34.2",
77
+ "@opentelemetry/resource-detector-container": "^0.3.2",
78
+ "@opentelemetry/resource-detector-gcp": "^0.29.2",
79
+ "@opentelemetry/resources": "^1.17.1",
80
+ "@opentelemetry/sdk-metrics": "^1.17.1",
82
81
  "@opentelemetry/sdk-node": "^0.43.0",
83
- "@opentelemetry/sdk-trace-base": "^1.17.0",
84
- "@opentelemetry/semantic-conventions": "^1.17.0",
82
+ "@opentelemetry/sdk-trace-base": "^1.17.1",
83
+ "@opentelemetry/semantic-conventions": "^1.17.1",
84
+ "@sesamecare-oss/opentelemetry-node-metrics": "^1.0.1",
85
85
  "cookie-parser": "^1.4.6",
86
86
  "dotenv": "^16.3.1",
87
87
  "eventsource": "^1.1.2",
@@ -90,7 +90,8 @@
90
90
  "glob": "^8.1.0",
91
91
  "lodash": "^4.17.21",
92
92
  "minimist": "^1.2.8",
93
- "pino": "^8.15.1",
93
+ "opentelemetry-instrumentation-fetch-node": "^1.1.0",
94
+ "pino": "^8.16.0",
94
95
  "read-pkg-up": "^7.0.1",
95
96
  "rest-api-support": "^1.16.3",
96
97
  "shortstop-dns": "^1.1.0",
@@ -98,7 +99,7 @@
98
99
  "shortstop-yaml": "^1.0.0"
99
100
  },
100
101
  "devDependencies": {
101
- "@commitlint/cli": "^17.7.1",
102
+ "@commitlint/cli": "^17.7.2",
102
103
  "@commitlint/config-conventional": "^17.7.0",
103
104
  "@openapi-typescript-infra/coconfig": "^4.2.1",
104
105
  "@semantic-release/changelog": "^6.0.3",
@@ -108,25 +109,25 @@
108
109
  "@semantic-release/release-notes-generator": "^12.0.0",
109
110
  "@types/cookie-parser": "^1.4.4",
110
111
  "@types/eventsource": "1.1.12",
111
- "@types/express": "^4.17.18",
112
+ "@types/express": "^4.17.19",
112
113
  "@types/glob": "^8.1.0",
113
114
  "@types/lodash": "^4.14.199",
114
115
  "@types/minimist": "^1.2.3",
115
- "@types/node": "^20.7.0",
116
- "@types/supertest": "^2.0.13",
117
- "@typescript-eslint/eslint-plugin": "^6.7.3",
118
- "@typescript-eslint/parser": "^6.7.3",
116
+ "@types/node": "^20.8.5",
117
+ "@types/supertest": "^2.0.14",
118
+ "@typescript-eslint/eslint-plugin": "^6.7.5",
119
+ "@typescript-eslint/parser": "^6.7.5",
119
120
  "coconfig": "^0.13.3",
120
- "eslint": "^8.50.0",
121
+ "eslint": "^8.51.0",
121
122
  "eslint-config-prettier": "^9.0.0",
122
123
  "eslint-plugin-import": "^2.28.1",
123
- "pino-pretty": "^10.2.0",
124
+ "pino-pretty": "^10.2.3",
124
125
  "pinst": "^3.0.0",
125
126
  "supertest": "^6.3.3",
126
127
  "ts-node": "^10.9.1",
127
128
  "tsconfig-paths": "^4.2.0",
128
129
  "typescript": "^5.2.2",
129
- "vitest": "^0.34.5"
130
+ "vitest": "^0.34.6"
130
131
  },
131
132
  "resolutions": {
132
133
  "qs": "^6.11.0"
@@ -53,7 +53,7 @@ async function addDefaultConfiguration(
53
53
 
54
54
  export interface ServiceConfigurationSpec {
55
55
  // Used for "sourcerequire" and other source-relative paths and for the package name
56
- rootDirectory: string;
56
+ sourceDirectory: string;
57
57
  // The LAST configuration is the most "specific" - if a configuration value
58
58
  // exists in all directories, the last one wins
59
59
  configurationDirectories: string[];
@@ -63,9 +63,9 @@ export interface ServiceConfigurationSpec {
63
63
  export async function loadConfiguration({
64
64
  name,
65
65
  configurationDirectories: dirs,
66
- rootDirectory,
66
+ sourceDirectory,
67
67
  }: ServiceConfigurationSpec): Promise<ConfigStore> {
68
- const defaultProtocols = shortstops({ name }, rootDirectory);
68
+ const defaultProtocols = shortstops({ name }, sourceDirectory);
69
69
  const specificConfig = dirs[dirs.length - 1];
70
70
 
71
71
  // This confit version just gets us environment info
@@ -7,6 +7,7 @@ import express from 'express';
7
7
  import { pino } from 'pino';
8
8
  import cookieParser from 'cookie-parser';
9
9
  import { metrics } from '@opentelemetry/api';
10
+ import { setupNodeMetrics } from '@sesamecare-oss/opentelemetry-node-metrics';
10
11
  import { createTerminus } from '@godaddy/terminus';
11
12
  import type { RequestHandler, Response } from 'express';
12
13
 
@@ -82,7 +83,7 @@ export async function startApp<
82
83
  const config = await loadConfiguration({
83
84
  name,
84
85
  configurationDirectories: options.configurationDirectories,
85
- rootDirectory,
86
+ sourceDirectory: path.join(rootDirectory, codepath),
86
87
  });
87
88
 
88
89
  const logging = config.get('logging') as ConfigurationSchema['logging'];
@@ -109,6 +110,8 @@ export async function startApp<
109
110
  }
110
111
 
111
112
  app.locals.meter = metrics.getMeterProvider().getMeter(name);
113
+ setupNodeMetrics(app.locals.meter, {});
114
+
112
115
  if (config.get('trustProxy')) {
113
116
  app.set('trust proxy', config.get('trustProxy'));
114
117
  }
@@ -10,7 +10,7 @@ const noop: SpanExporter['export'] = (spans, resultCallback) => {
10
10
  };
11
11
 
12
12
  export class DummySpanExporter implements SpanExporter {
13
- export = noop;
13
+ export: typeof noop = noop;
14
14
 
15
15
  async shutdown() {
16
16
  // Nothing to do
@@ -1,6 +1,7 @@
1
1
  import type { Instrumentation } from '@opentelemetry/instrumentation';
2
2
  import { DnsInstrumentation } from '@opentelemetry/instrumentation-dns';
3
3
  import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
4
+ import { FetchInstrumentation } from 'opentelemetry-instrumentation-fetch-node';
4
5
  import { GenericPoolInstrumentation } from '@opentelemetry/instrumentation-generic-pool';
5
6
  import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
6
7
  import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
@@ -18,13 +19,11 @@ import {
18
19
  processDetectorSync,
19
20
  } from '@opentelemetry/resources';
20
21
 
21
- import { FetchInstrumentation } from './fetchInstrumentation';
22
-
23
22
  const InstrumentationMap = {
24
23
  '@opentelemetry/instrumentation-http': HttpInstrumentation,
25
- 'opentelemetry-instrumentation-node-18-fetch': FetchInstrumentation,
26
24
  '@opentelemetry/instrumentation-dns': DnsInstrumentation,
27
25
  '@opentelemetry/instrumentation-express': ExpressInstrumentation,
26
+ 'opentelemetry-instrumentation-fetch-node': FetchInstrumentation,
28
27
  '@opentelemetry/instrumentation-generic-pool': GenericPoolInstrumentation,
29
28
  '@opentelemetry/instrumentation-ioredis': IORedisInstrumentation,
30
29
  '@opentelemetry/instrumentation-net': NetInstrumentation,
@@ -1,49 +0,0 @@
1
- /// <reference types="node" />
2
- import { Instrumentation, InstrumentationConfig } from '@opentelemetry/instrumentation';
3
- import { MeterProvider, Span, TracerProvider } from '@opentelemetry/api';
4
- interface FetchRequestArguments {
5
- request: {
6
- path: string;
7
- method: string;
8
- origin: string;
9
- headers: string;
10
- };
11
- span: Span;
12
- additionalHeaders: Record<string, unknown>;
13
- error?: Error;
14
- }
15
- type ErrorFetchRequestArguments = FetchRequestArguments & {
16
- error: Error;
17
- };
18
- type ResponseFetchRequestArguments = FetchRequestArguments & {
19
- response: {
20
- statusCode: number;
21
- headers: Buffer[];
22
- };
23
- };
24
- interface FetchInstrumentationConfig extends InstrumentationConfig {
25
- onRequest?: (args: FetchRequestArguments) => void;
26
- }
27
- export declare class FetchInstrumentation implements Instrumentation {
28
- private channelSubs;
29
- private spanFromReq;
30
- private tracer;
31
- private config;
32
- private meter;
33
- readonly instrumentationName = "opentelemetry-instrumentation-node-18-fetch";
34
- readonly instrumentationVersion = "1.0.0";
35
- readonly instrumentationDescription = "Instrumentation for Node 18 fetch via diagnostics_channel";
36
- private subscribeToChannel;
37
- constructor(config: FetchInstrumentationConfig);
38
- disable(): void;
39
- enable(): void;
40
- setTracerProvider(tracerProvider: TracerProvider): void;
41
- setMeterProvider(meterProvider: MeterProvider): void;
42
- setConfig(config: InstrumentationConfig): void;
43
- getConfig(): InstrumentationConfig;
44
- onRequest({ request }: FetchRequestArguments): void;
45
- onHeaders({ request, response }: ResponseFetchRequestArguments): void;
46
- onDone({ request }: FetchRequestArguments): void;
47
- onError({ request, error }: ErrorFetchRequestArguments): void;
48
- }
49
- export {};
@@ -1,143 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.FetchInstrumentation = void 0;
7
- const node_diagnostics_channel_1 = __importDefault(require("node:diagnostics_channel"));
8
- const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
9
- const api_1 = require("@opentelemetry/api");
10
- // Get the content-length from undici response headers.
11
- // `headers` is an Array of buffers: [k, v, k, v, ...].
12
- // If the header is not present, or has an invalid value, this returns null.
13
- function contentLengthFromResponseHeaders(headers) {
14
- const name = 'content-length';
15
- for (let i = 0; i < headers.length; i += 2) {
16
- const k = headers[i];
17
- if (k.length === name.length && k.toString().toLowerCase() === name) {
18
- const v = Number(headers[i + 1]);
19
- if (!Number.isNaN(Number(v))) {
20
- return v;
21
- }
22
- return undefined;
23
- }
24
- }
25
- return undefined;
26
- }
27
- // A combination of https://github.com/elastic/apm-agent-nodejs and
28
- // https://github.com/gadget-inc/opentelemetry-instrumentations/blob/main/packages/opentelemetry-instrumentation-undici/src/index.ts
29
- class FetchInstrumentation {
30
- // Keep ref to avoid https://github.com/nodejs/node/issues/42170 bug and for
31
- // unsubscribing.
32
- channelSubs;
33
- spanFromReq = new WeakMap();
34
- tracer;
35
- config;
36
- meter;
37
- instrumentationName = 'opentelemetry-instrumentation-node-18-fetch';
38
- instrumentationVersion = '1.0.0';
39
- instrumentationDescription = 'Instrumentation for Node 18 fetch via diagnostics_channel';
40
- subscribeToChannel(diagnosticChannel, onMessage) {
41
- const channel = node_diagnostics_channel_1.default.channel(diagnosticChannel);
42
- channel.subscribe(onMessage);
43
- this.channelSubs.push({
44
- name: diagnosticChannel,
45
- channel,
46
- onMessage,
47
- });
48
- }
49
- constructor(config) {
50
- // Force load fetch API (since it's lazy loaded in Node 18)
51
- fetch('').catch(() => Promise.resolve());
52
- this.channelSubs = [];
53
- this.meter = api_1.metrics.getMeter(this.instrumentationName, this.instrumentationVersion);
54
- this.tracer = api_1.trace.getTracer(this.instrumentationName, this.instrumentationVersion);
55
- this.config = { ...config };
56
- }
57
- disable() {
58
- this.channelSubs?.forEach((sub) => sub.channel.unsubscribe(sub.onMessage));
59
- }
60
- enable() {
61
- this.subscribeToChannel('undici:request:create', (args) => this.onRequest(args));
62
- this.subscribeToChannel('undici:request:headers', (args) => this.onHeaders(args));
63
- this.subscribeToChannel('undici:request:trailers', (args) => this.onDone(args));
64
- this.subscribeToChannel('undici:request:error', (args) => this.onError(args));
65
- }
66
- setTracerProvider(tracerProvider) {
67
- this.tracer = tracerProvider.getTracer(this.instrumentationName, this.instrumentationVersion);
68
- }
69
- setMeterProvider(meterProvider) {
70
- this.meter = meterProvider.getMeter(this.instrumentationName, this.instrumentationVersion);
71
- }
72
- setConfig(config) {
73
- this.config = { ...config };
74
- }
75
- getConfig() {
76
- return this.config;
77
- }
78
- onRequest({ request }) {
79
- // We do not handle instrumenting HTTP CONNECT. See limitation notes above.
80
- if (request.method === 'CONNECT') {
81
- return;
82
- }
83
- const span = this.tracer.startSpan(`HTTP ${request.method}`, {
84
- kind: api_1.SpanKind.CLIENT,
85
- attributes: {
86
- [semantic_conventions_1.SemanticAttributes.HTTP_URL]: String(request.origin),
87
- [semantic_conventions_1.SemanticAttributes.HTTP_METHOD]: request.method,
88
- [semantic_conventions_1.SemanticAttributes.HTTP_TARGET]: request.path,
89
- 'http.client': 'fetch',
90
- },
91
- });
92
- const requestContext = api_1.trace.setSpan(api_1.context.active(), span);
93
- const addedHeaders = {};
94
- api_1.propagation.inject(requestContext, addedHeaders);
95
- if (this.config.onRequest) {
96
- this.config.onRequest({ request, span, additionalHeaders: addedHeaders });
97
- }
98
- request.headers += Object.entries(addedHeaders)
99
- .map(([k, v]) => `${k}: ${v}\r\n`)
100
- .join('');
101
- this.spanFromReq.set(request, span);
102
- }
103
- onHeaders({ request, response }) {
104
- const span = this.spanFromReq.get(request);
105
- if (span !== undefined) {
106
- // We are currently *not* capturing response headers, even though the
107
- // intake API does allow it, because none of the other `setHttpContext`
108
- // uses currently do.
109
- const cLen = contentLengthFromResponseHeaders(response.headers);
110
- const attrs = {
111
- [semantic_conventions_1.SemanticAttributes.HTTP_STATUS_CODE]: response.statusCode,
112
- };
113
- if (cLen) {
114
- attrs[semantic_conventions_1.SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH] = cLen;
115
- }
116
- span.setAttributes(attrs);
117
- span.setStatus({
118
- code: response.statusCode >= 400 ? api_1.SpanStatusCode.ERROR : api_1.SpanStatusCode.OK,
119
- message: String(response.statusCode),
120
- });
121
- }
122
- }
123
- onDone({ request }) {
124
- const span = this.spanFromReq.get(request);
125
- if (span !== undefined) {
126
- span.end();
127
- this.spanFromReq.delete(request);
128
- }
129
- }
130
- onError({ request, error }) {
131
- const span = this.spanFromReq.get(request);
132
- if (span !== undefined) {
133
- span.recordException(error);
134
- span.setStatus({
135
- code: api_1.SpanStatusCode.ERROR,
136
- message: error.message,
137
- });
138
- span.end();
139
- }
140
- }
141
- }
142
- exports.FetchInstrumentation = FetchInstrumentation;
143
- //# sourceMappingURL=fetchInstrumentation.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetchInstrumentation.js","sourceRoot":"","sources":["../../src/telemetry/fetchInstrumentation.ts"],"names":[],"mappings":"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"}
@@ -1,214 +0,0 @@
1
- import diagch from 'node:diagnostics_channel';
2
-
3
- import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
4
- import { Instrumentation, InstrumentationConfig } from '@opentelemetry/instrumentation';
5
- import {
6
- Attributes,
7
- context,
8
- propagation,
9
- metrics,
10
- Meter,
11
- MeterProvider,
12
- Span,
13
- SpanKind,
14
- SpanStatusCode,
15
- trace,
16
- Tracer,
17
- TracerProvider,
18
- } from '@opentelemetry/api';
19
-
20
- interface ListenerRecord {
21
- name: string;
22
- channel: diagch.Channel;
23
- onMessage: diagch.ChannelListener;
24
- }
25
-
26
- interface FetchRequestArguments {
27
- request: {
28
- path: string;
29
- method: string;
30
- origin: string;
31
- headers: string;
32
- };
33
- span: Span;
34
- additionalHeaders: Record<string, unknown>;
35
- error?: Error;
36
- }
37
-
38
- type ErrorFetchRequestArguments = FetchRequestArguments & { error: Error };
39
- type ResponseFetchRequestArguments = FetchRequestArguments & {
40
- response: {
41
- statusCode: number;
42
- headers: Buffer[];
43
- };
44
- };
45
-
46
- interface FetchInstrumentationConfig extends InstrumentationConfig {
47
- onRequest?: (args: FetchRequestArguments) => void;
48
- }
49
-
50
- // Get the content-length from undici response headers.
51
- // `headers` is an Array of buffers: [k, v, k, v, ...].
52
- // If the header is not present, or has an invalid value, this returns null.
53
- function contentLengthFromResponseHeaders(headers: Buffer[]) {
54
- const name = 'content-length';
55
- for (let i = 0; i < headers.length; i += 2) {
56
- const k = headers[i];
57
- if (k.length === name.length && k.toString().toLowerCase() === name) {
58
- const v = Number(headers[i + 1]);
59
- if (!Number.isNaN(Number(v))) {
60
- return v;
61
- }
62
- return undefined;
63
- }
64
- }
65
- return undefined;
66
- }
67
-
68
- // A combination of https://github.com/elastic/apm-agent-nodejs and
69
- // https://github.com/gadget-inc/opentelemetry-instrumentations/blob/main/packages/opentelemetry-instrumentation-undici/src/index.ts
70
- export class FetchInstrumentation implements Instrumentation {
71
- // Keep ref to avoid https://github.com/nodejs/node/issues/42170 bug and for
72
- // unsubscribing.
73
- private channelSubs: ListenerRecord[];
74
-
75
- private spanFromReq = new WeakMap<object, Span>();
76
-
77
- private tracer: Tracer;
78
-
79
- private config: FetchInstrumentationConfig;
80
-
81
- private meter: Meter;
82
-
83
- public readonly instrumentationName = 'opentelemetry-instrumentation-node-18-fetch';
84
-
85
- public readonly instrumentationVersion = '1.0.0';
86
-
87
- public readonly instrumentationDescription =
88
- 'Instrumentation for Node 18 fetch via diagnostics_channel';
89
-
90
- private subscribeToChannel(diagnosticChannel: string, onMessage: diagch.ChannelListener) {
91
- const channel = diagch.channel(diagnosticChannel);
92
- channel.subscribe(onMessage);
93
- this.channelSubs.push({
94
- name: diagnosticChannel,
95
- channel,
96
- onMessage,
97
- });
98
- }
99
-
100
- constructor(config: FetchInstrumentationConfig) {
101
- // Force load fetch API (since it's lazy loaded in Node 18)
102
- fetch('').catch(() => Promise.resolve());
103
- this.channelSubs = [];
104
- this.meter = metrics.getMeter(this.instrumentationName, this.instrumentationVersion);
105
- this.tracer = trace.getTracer(this.instrumentationName, this.instrumentationVersion);
106
- this.config = { ...config };
107
- }
108
-
109
- disable(): void {
110
- this.channelSubs?.forEach((sub) => sub.channel.unsubscribe(sub.onMessage));
111
- }
112
-
113
- enable(): void {
114
- this.subscribeToChannel('undici:request:create', (args) =>
115
- this.onRequest(args as FetchRequestArguments),
116
- );
117
- this.subscribeToChannel('undici:request:headers', (args) =>
118
- this.onHeaders(args as ResponseFetchRequestArguments),
119
- );
120
- this.subscribeToChannel('undici:request:trailers', (args) =>
121
- this.onDone(args as FetchRequestArguments),
122
- );
123
- this.subscribeToChannel('undici:request:error', (args) =>
124
- this.onError(args as ErrorFetchRequestArguments),
125
- );
126
- }
127
-
128
- setTracerProvider(tracerProvider: TracerProvider): void {
129
- this.tracer = tracerProvider.getTracer(this.instrumentationName, this.instrumentationVersion);
130
- }
131
-
132
- public setMeterProvider(meterProvider: MeterProvider): void {
133
- this.meter = meterProvider.getMeter(this.instrumentationName, this.instrumentationVersion);
134
- }
135
-
136
- setConfig(config: InstrumentationConfig): void {
137
- this.config = { ...config };
138
- }
139
-
140
- getConfig(): InstrumentationConfig {
141
- return this.config;
142
- }
143
-
144
- onRequest({ request }: FetchRequestArguments): void {
145
- // We do not handle instrumenting HTTP CONNECT. See limitation notes above.
146
- if (request.method === 'CONNECT') {
147
- return;
148
- }
149
- const span = this.tracer.startSpan(`HTTP ${request.method}`, {
150
- kind: SpanKind.CLIENT,
151
- attributes: {
152
- [SemanticAttributes.HTTP_URL]: String(request.origin),
153
- [SemanticAttributes.HTTP_METHOD]: request.method,
154
- [SemanticAttributes.HTTP_TARGET]: request.path,
155
- 'http.client': 'fetch',
156
- },
157
- });
158
- const requestContext = trace.setSpan(context.active(), span);
159
- const addedHeaders: Record<string, string> = {};
160
- propagation.inject(requestContext, addedHeaders);
161
-
162
- if (this.config.onRequest) {
163
- this.config.onRequest({ request, span, additionalHeaders: addedHeaders });
164
- }
165
-
166
- request.headers += Object.entries(addedHeaders)
167
- .map(([k, v]) => `${k}: ${v}\r\n`)
168
- .join('');
169
- this.spanFromReq.set(request, span);
170
- }
171
-
172
- onHeaders({ request, response }: ResponseFetchRequestArguments): void {
173
- const span = this.spanFromReq.get(request);
174
-
175
- if (span !== undefined) {
176
- // We are currently *not* capturing response headers, even though the
177
- // intake API does allow it, because none of the other `setHttpContext`
178
- // uses currently do.
179
-
180
- const cLen = contentLengthFromResponseHeaders(response.headers);
181
- const attrs: Attributes = {
182
- [SemanticAttributes.HTTP_STATUS_CODE]: response.statusCode,
183
- };
184
- if (cLen) {
185
- attrs[SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH] = cLen;
186
- }
187
- span.setAttributes(attrs);
188
- span.setStatus({
189
- code: response.statusCode >= 400 ? SpanStatusCode.ERROR : SpanStatusCode.OK,
190
- message: String(response.statusCode),
191
- });
192
- }
193
- }
194
-
195
- onDone({ request }: FetchRequestArguments): void {
196
- const span = this.spanFromReq.get(request);
197
- if (span !== undefined) {
198
- span.end();
199
- this.spanFromReq.delete(request);
200
- }
201
- }
202
-
203
- onError({ request, error }: ErrorFetchRequestArguments): void {
204
- const span = this.spanFromReq.get(request);
205
- if (span !== undefined) {
206
- span.recordException(error);
207
- span.setStatus({
208
- code: SpanStatusCode.ERROR,
209
- message: error.message,
210
- });
211
- span.end();
212
- }
213
- }
214
- }