@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/.trunk/trunk.yaml +4 -4
- package/CHANGELOG.md +20 -0
- package/build/config/index.d.ts +2 -2
- package/build/config/index.js +2 -2
- package/build/config/index.js.map +1 -1
- package/build/express-app/app.js +3 -1
- package/build/express-app/app.js.map +1 -1
- package/build/telemetry/DummyExporter.d.ts +3 -1
- package/build/telemetry/DummyExporter.js.map +1 -1
- package/build/telemetry/instrumentations.d.ts +2 -2
- package/build/telemetry/instrumentations.js +2 -2
- package/build/telemetry/instrumentations.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +27 -26
- package/src/config/index.ts +3 -3
- package/src/express-app/app.ts +4 -1
- package/src/telemetry/DummyExporter.ts +1 -1
- package/src/telemetry/instrumentations.ts +2 -3
- package/build/telemetry/fetchInstrumentation.d.ts +0 -49
- package/build/telemetry/fetchInstrumentation.js +0 -143
- package/build/telemetry/fetchInstrumentation.js.map +0 -1
- package/src/telemetry/fetchInstrumentation.ts +0 -214
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openapi-typescript-infra/service",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
69
|
-
"@opentelemetry/instrumentation-express": "^0.33.
|
|
70
|
-
"@opentelemetry/instrumentation-
|
|
71
|
-
"@opentelemetry/instrumentation-
|
|
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.
|
|
75
|
-
"@opentelemetry/instrumentation-net": "^0.32.
|
|
76
|
-
"@opentelemetry/instrumentation-pg": "^0.36.
|
|
77
|
-
"@opentelemetry/instrumentation-pino": "^0.34.
|
|
78
|
-
"@opentelemetry/resource-detector-container": "^0.3.
|
|
79
|
-
"@opentelemetry/resource-detector-gcp": "^0.29.
|
|
80
|
-
"@opentelemetry/resources": "^1.17.
|
|
81
|
-
"@opentelemetry/sdk-metrics": "^1.17.
|
|
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.
|
|
84
|
-
"@opentelemetry/semantic-conventions": "^1.17.
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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.
|
|
116
|
-
"@types/supertest": "^2.0.
|
|
117
|
-
"@typescript-eslint/eslint-plugin": "^6.7.
|
|
118
|
-
"@typescript-eslint/parser": "^6.7.
|
|
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.
|
|
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.
|
|
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.
|
|
130
|
+
"vitest": "^0.34.6"
|
|
130
131
|
},
|
|
131
132
|
"resolutions": {
|
|
132
133
|
"qs": "^6.11.0"
|
package/src/config/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
66
|
+
sourceDirectory,
|
|
67
67
|
}: ServiceConfigurationSpec): Promise<ConfigStore> {
|
|
68
|
-
const defaultProtocols = shortstops({ name },
|
|
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
|
package/src/express-app/app.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -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
|
-
}
|