@sentienguard/apm 1.0.19 → 1.0.21
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 +1 -1
- package/src/aggregator.js +465 -465
- package/src/spanExporter.js +232 -232
- package/src/traceSpanExporter.js +186 -165
- package/src/tracing.js +230 -230
package/src/tracing.js
CHANGED
|
@@ -1,230 +1,230 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenTelemetry bootstrap: W3C trace context propagation + HTTP/Express spans.
|
|
3
|
-
* Spans are exported through SentienGuardSpanExporter into the existing metrics aggregator.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { trace } from '@opentelemetry/api';
|
|
7
|
-
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
8
|
-
import { Resource } from '@opentelemetry/resources';
|
|
9
|
-
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_DEPLOYMENT_ENVIRONMENT } from '@opentelemetry/semantic-conventions';
|
|
10
|
-
import { W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
11
|
-
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
12
|
-
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
|
|
13
|
-
import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';
|
|
14
|
-
import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose';
|
|
15
|
-
import { AlwaysOnSampler, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
16
|
-
import { getConfig, debug } from './config.js';
|
|
17
|
-
import { SentienGuardSpanExporter } from './spanExporter.js';
|
|
18
|
-
import { SentienGuardTraceSpanExporter } from './traceSpanExporter.js';
|
|
19
|
-
|
|
20
|
-
let sdk = null;
|
|
21
|
-
let tracingActive = false;
|
|
22
|
-
|
|
23
|
-
function setAttr(span, key, value) {
|
|
24
|
-
try {
|
|
25
|
-
if (!span || typeof span.setAttribute !== 'function') return;
|
|
26
|
-
if (value == null) return;
|
|
27
|
-
const v = typeof value === 'string' ? value : String(value);
|
|
28
|
-
if (!v) return;
|
|
29
|
-
span.setAttribute(key, v);
|
|
30
|
-
} catch {
|
|
31
|
-
// ignore
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function setAttrNumber(span, key, value) {
|
|
36
|
-
try {
|
|
37
|
-
if (!span || typeof span.setAttribute !== 'function') return;
|
|
38
|
-
const n = Number(value);
|
|
39
|
-
if (!Number.isFinite(n)) return;
|
|
40
|
-
span.setAttribute(key, n);
|
|
41
|
-
} catch {
|
|
42
|
-
// ignore
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function safeUrlFromOutgoingRequest(requestOptions) {
|
|
47
|
-
try {
|
|
48
|
-
if (!requestOptions) return '';
|
|
49
|
-
if (typeof requestOptions === 'string') return requestOptions;
|
|
50
|
-
if (requestOptions instanceof URL) return requestOptions.toString();
|
|
51
|
-
const protocol = requestOptions.protocol || 'http:';
|
|
52
|
-
const hostRaw = requestOptions.hostname || requestOptions.host || '';
|
|
53
|
-
const host = String(hostRaw).trim();
|
|
54
|
-
const path = requestOptions.path || requestOptions.pathname || '/';
|
|
55
|
-
if (!host) return '';
|
|
56
|
-
return `${protocol}//${host}${path}`;
|
|
57
|
-
} catch {
|
|
58
|
-
return '';
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function safeHostFromIncoming(req) {
|
|
63
|
-
try {
|
|
64
|
-
const h = req?.headers?.host;
|
|
65
|
-
return h ? String(h).split(',')[0].trim() : '';
|
|
66
|
-
} catch {
|
|
67
|
-
return '';
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function shouldIgnoreOutgoingHost(hostname) {
|
|
72
|
-
if (!hostname) return false;
|
|
73
|
-
const host = hostname.split(':')[0];
|
|
74
|
-
const cfg = getConfig();
|
|
75
|
-
if (cfg.tracing?.traceLocalHttp && (host === 'localhost' || host === '127.0.0.1' || host === '::1')) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
if (host === 'localhost' || host === '127.0.0.1' || host === '::1') return true;
|
|
79
|
-
try {
|
|
80
|
-
if (cfg.endpoint) {
|
|
81
|
-
const h = new URL(cfg.endpoint).hostname;
|
|
82
|
-
if (host === h) return true;
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
// ignore
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Start OpenTelemetry when enabled in config. Returns true if tracing owns HTTP instrumentation.
|
|
92
|
-
*/
|
|
93
|
-
export function startTracing() {
|
|
94
|
-
const cfg = getConfig();
|
|
95
|
-
if (!cfg.tracing?.enabled) {
|
|
96
|
-
debug('OpenTelemetry tracing disabled (SENTIENGUARD_TRACING=false)');
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (tracingActive) {
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const resource = new Resource({
|
|
106
|
-
[SEMRESATTRS_SERVICE_NAME]: cfg.service,
|
|
107
|
-
[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: cfg.environment
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const metricsExporter = new SentienGuardSpanExporter();
|
|
111
|
-
const traceExporter = new SentienGuardTraceSpanExporter();
|
|
112
|
-
|
|
113
|
-
const httpInstrumentation = new HttpInstrumentation({
|
|
114
|
-
ignoreOutgoingRequestHook: (requestOptions) => {
|
|
115
|
-
const host = requestOptions.hostname || (requestOptions.host ? String(requestOptions.host).split(':')[0] : '');
|
|
116
|
-
return shouldIgnoreOutgoingHost(host);
|
|
117
|
-
},
|
|
118
|
-
/**
|
|
119
|
-
* Ensure we always attach basic HTTP attributes.
|
|
120
|
-
* This protects us from cases where the underlying instrumentation
|
|
121
|
-
* doesn't populate attributes (common in some Node/Jest/undici combos).
|
|
122
|
-
*/
|
|
123
|
-
requestHook: (span, request) => {
|
|
124
|
-
// Incoming request (SERVER)
|
|
125
|
-
if (request && typeof request === 'object' && 'headers' in request && 'method' in request) {
|
|
126
|
-
setAttr(span, 'http.method', request.method);
|
|
127
|
-
setAttr(span, 'http.target', request.url);
|
|
128
|
-
setAttr(span, 'http.host', safeHostFromIncoming(request));
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Outgoing request (CLIENT) — requestOptions
|
|
133
|
-
setAttr(span, 'http.method', request?.method);
|
|
134
|
-
const url = safeUrlFromOutgoingRequest(request);
|
|
135
|
-
if (url) setAttr(span, 'http.url', url);
|
|
136
|
-
const host = request?.hostname || (request?.host ? String(request.host).split(':')[0] : '');
|
|
137
|
-
if (host) setAttr(span, 'net.peer.name', host);
|
|
138
|
-
if (request?.port) setAttrNumber(span, 'net.peer.port', request.port);
|
|
139
|
-
},
|
|
140
|
-
responseHook: (span, response) => {
|
|
141
|
-
// Incoming response (SERVER): ServerResponse
|
|
142
|
-
if (response && typeof response === 'object' && 'statusCode' in response) {
|
|
143
|
-
setAttrNumber(span, 'http.status_code', response.statusCode);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
,
|
|
147
|
-
/**
|
|
148
|
-
* More reliable cross-version hook that runs with both request+response context.
|
|
149
|
-
* Used as a safety net to guarantee basic attributes exist on spans.
|
|
150
|
-
*/
|
|
151
|
-
applyCustomAttributesOnSpan: (span, request, response) => {
|
|
152
|
-
// Incoming request (SERVER)
|
|
153
|
-
if (request && typeof request === 'object' && 'headers' in request && 'method' in request) {
|
|
154
|
-
setAttr(span, 'http.method', request.method);
|
|
155
|
-
setAttr(span, 'http.target', request.url);
|
|
156
|
-
setAttr(span, 'http.host', safeHostFromIncoming(request));
|
|
157
|
-
} else {
|
|
158
|
-
// Outgoing request (CLIENT)
|
|
159
|
-
setAttr(span, 'http.method', request?.method);
|
|
160
|
-
const url = safeUrlFromOutgoingRequest(request);
|
|
161
|
-
if (url) setAttr(span, 'http.url', url);
|
|
162
|
-
const host = request?.hostname || (request?.host ? String(request.host).split(':')[0] : '');
|
|
163
|
-
if (host) setAttr(span, 'net.peer.name', host);
|
|
164
|
-
if (request?.port) setAttrNumber(span, 'net.peer.port', request.port);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (response && typeof response === 'object' && 'statusCode' in response) {
|
|
168
|
-
setAttrNumber(span, 'http.status_code', response.statusCode);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const expressInstrumentation = new ExpressInstrumentation();
|
|
174
|
-
const mongoInstrumentation = new MongoDBInstrumentation();
|
|
175
|
-
const mongooseInstrumentation = new MongooseInstrumentation();
|
|
176
|
-
|
|
177
|
-
sdk = new NodeSDK({
|
|
178
|
-
resource,
|
|
179
|
-
// Important: keep span recording ON so APM metrics derived from spans are not undersampled.
|
|
180
|
-
// Raw trace export sampling is handled inside SentienGuardTraceSpanExporter instead.
|
|
181
|
-
sampler: new AlwaysOnSampler(),
|
|
182
|
-
textMapPropagator: new W3CTraceContextPropagator(),
|
|
183
|
-
// Include MongoDB/Mongoose instrumentations for "zero-code" DB spans.
|
|
184
|
-
// These are safe even when the app doesn't use MongoDB; they patch when modules are loaded.
|
|
185
|
-
instrumentations: [httpInstrumentation, expressInstrumentation, mongoInstrumentation, mongooseInstrumentation],
|
|
186
|
-
spanProcessors: [
|
|
187
|
-
new BatchSpanProcessor(metricsExporter),
|
|
188
|
-
new BatchSpanProcessor(traceExporter)
|
|
189
|
-
],
|
|
190
|
-
autoDetectResources: false
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
sdk.start();
|
|
194
|
-
tracingActive = true;
|
|
195
|
-
debug('OpenTelemetry tracing started (W3C Trace Context + HTTP/Express)');
|
|
196
|
-
return true;
|
|
197
|
-
} catch (err) {
|
|
198
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
199
|
-
debug(`OpenTelemetry failed to start, falling back to legacy instrumentation: ${msg}`);
|
|
200
|
-
sdk = null;
|
|
201
|
-
tracingActive = false;
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export function isTracingActive() {
|
|
207
|
-
return tracingActive;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** Hex trace id for the active span (log correlation); undefined if none. */
|
|
211
|
-
export function getActiveTraceId() {
|
|
212
|
-
const s = trace.getActiveSpan();
|
|
213
|
-
if (!s) return undefined;
|
|
214
|
-
const c = s.spanContext();
|
|
215
|
-
return c.traceId && c.traceFlags !== undefined ? c.traceId : undefined;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export async function shutdownTracing() {
|
|
219
|
-
if (!sdk) return;
|
|
220
|
-
try {
|
|
221
|
-
await sdk.shutdown();
|
|
222
|
-
debug('OpenTelemetry tracing shut down');
|
|
223
|
-
} catch (err) {
|
|
224
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
225
|
-
debug(`OpenTelemetry shutdown error: ${msg}`);
|
|
226
|
-
} finally {
|
|
227
|
-
sdk = null;
|
|
228
|
-
tracingActive = false;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry bootstrap: W3C trace context propagation + HTTP/Express spans.
|
|
3
|
+
* Spans are exported through SentienGuardSpanExporter into the existing metrics aggregator.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { trace } from '@opentelemetry/api';
|
|
7
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
8
|
+
import { Resource } from '@opentelemetry/resources';
|
|
9
|
+
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_DEPLOYMENT_ENVIRONMENT } from '@opentelemetry/semantic-conventions';
|
|
10
|
+
import { W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
11
|
+
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
12
|
+
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
|
|
13
|
+
import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';
|
|
14
|
+
import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose';
|
|
15
|
+
import { AlwaysOnSampler, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
16
|
+
import { getConfig, debug } from './config.js';
|
|
17
|
+
import { SentienGuardSpanExporter } from './spanExporter.js';
|
|
18
|
+
import { SentienGuardTraceSpanExporter } from './traceSpanExporter.js';
|
|
19
|
+
|
|
20
|
+
let sdk = null;
|
|
21
|
+
let tracingActive = false;
|
|
22
|
+
|
|
23
|
+
function setAttr(span, key, value) {
|
|
24
|
+
try {
|
|
25
|
+
if (!span || typeof span.setAttribute !== 'function') return;
|
|
26
|
+
if (value == null) return;
|
|
27
|
+
const v = typeof value === 'string' ? value : String(value);
|
|
28
|
+
if (!v) return;
|
|
29
|
+
span.setAttribute(key, v);
|
|
30
|
+
} catch {
|
|
31
|
+
// ignore
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function setAttrNumber(span, key, value) {
|
|
36
|
+
try {
|
|
37
|
+
if (!span || typeof span.setAttribute !== 'function') return;
|
|
38
|
+
const n = Number(value);
|
|
39
|
+
if (!Number.isFinite(n)) return;
|
|
40
|
+
span.setAttribute(key, n);
|
|
41
|
+
} catch {
|
|
42
|
+
// ignore
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function safeUrlFromOutgoingRequest(requestOptions) {
|
|
47
|
+
try {
|
|
48
|
+
if (!requestOptions) return '';
|
|
49
|
+
if (typeof requestOptions === 'string') return requestOptions;
|
|
50
|
+
if (requestOptions instanceof URL) return requestOptions.toString();
|
|
51
|
+
const protocol = requestOptions.protocol || 'http:';
|
|
52
|
+
const hostRaw = requestOptions.hostname || requestOptions.host || '';
|
|
53
|
+
const host = String(hostRaw).trim();
|
|
54
|
+
const path = requestOptions.path || requestOptions.pathname || '/';
|
|
55
|
+
if (!host) return '';
|
|
56
|
+
return `${protocol}//${host}${path}`;
|
|
57
|
+
} catch {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function safeHostFromIncoming(req) {
|
|
63
|
+
try {
|
|
64
|
+
const h = req?.headers?.host;
|
|
65
|
+
return h ? String(h).split(',')[0].trim() : '';
|
|
66
|
+
} catch {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function shouldIgnoreOutgoingHost(hostname) {
|
|
72
|
+
if (!hostname) return false;
|
|
73
|
+
const host = hostname.split(':')[0];
|
|
74
|
+
const cfg = getConfig();
|
|
75
|
+
if (cfg.tracing?.traceLocalHttp && (host === 'localhost' || host === '127.0.0.1' || host === '::1')) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (host === 'localhost' || host === '127.0.0.1' || host === '::1') return true;
|
|
79
|
+
try {
|
|
80
|
+
if (cfg.endpoint) {
|
|
81
|
+
const h = new URL(cfg.endpoint).hostname;
|
|
82
|
+
if (host === h) return true;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// ignore
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Start OpenTelemetry when enabled in config. Returns true if tracing owns HTTP instrumentation.
|
|
92
|
+
*/
|
|
93
|
+
export function startTracing() {
|
|
94
|
+
const cfg = getConfig();
|
|
95
|
+
if (!cfg.tracing?.enabled) {
|
|
96
|
+
debug('OpenTelemetry tracing disabled (SENTIENGUARD_TRACING=false)');
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (tracingActive) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const resource = new Resource({
|
|
106
|
+
[SEMRESATTRS_SERVICE_NAME]: cfg.service,
|
|
107
|
+
[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: cfg.environment
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const metricsExporter = new SentienGuardSpanExporter();
|
|
111
|
+
const traceExporter = new SentienGuardTraceSpanExporter();
|
|
112
|
+
|
|
113
|
+
const httpInstrumentation = new HttpInstrumentation({
|
|
114
|
+
ignoreOutgoingRequestHook: (requestOptions) => {
|
|
115
|
+
const host = requestOptions.hostname || (requestOptions.host ? String(requestOptions.host).split(':')[0] : '');
|
|
116
|
+
return shouldIgnoreOutgoingHost(host);
|
|
117
|
+
},
|
|
118
|
+
/**
|
|
119
|
+
* Ensure we always attach basic HTTP attributes.
|
|
120
|
+
* This protects us from cases where the underlying instrumentation
|
|
121
|
+
* doesn't populate attributes (common in some Node/Jest/undici combos).
|
|
122
|
+
*/
|
|
123
|
+
requestHook: (span, request) => {
|
|
124
|
+
// Incoming request (SERVER)
|
|
125
|
+
if (request && typeof request === 'object' && 'headers' in request && 'method' in request) {
|
|
126
|
+
setAttr(span, 'http.method', request.method);
|
|
127
|
+
setAttr(span, 'http.target', request.url);
|
|
128
|
+
setAttr(span, 'http.host', safeHostFromIncoming(request));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Outgoing request (CLIENT) — requestOptions
|
|
133
|
+
setAttr(span, 'http.method', request?.method);
|
|
134
|
+
const url = safeUrlFromOutgoingRequest(request);
|
|
135
|
+
if (url) setAttr(span, 'http.url', url);
|
|
136
|
+
const host = request?.hostname || (request?.host ? String(request.host).split(':')[0] : '');
|
|
137
|
+
if (host) setAttr(span, 'net.peer.name', host);
|
|
138
|
+
if (request?.port) setAttrNumber(span, 'net.peer.port', request.port);
|
|
139
|
+
},
|
|
140
|
+
responseHook: (span, response) => {
|
|
141
|
+
// Incoming response (SERVER): ServerResponse
|
|
142
|
+
if (response && typeof response === 'object' && 'statusCode' in response) {
|
|
143
|
+
setAttrNumber(span, 'http.status_code', response.statusCode);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
,
|
|
147
|
+
/**
|
|
148
|
+
* More reliable cross-version hook that runs with both request+response context.
|
|
149
|
+
* Used as a safety net to guarantee basic attributes exist on spans.
|
|
150
|
+
*/
|
|
151
|
+
applyCustomAttributesOnSpan: (span, request, response) => {
|
|
152
|
+
// Incoming request (SERVER)
|
|
153
|
+
if (request && typeof request === 'object' && 'headers' in request && 'method' in request) {
|
|
154
|
+
setAttr(span, 'http.method', request.method);
|
|
155
|
+
setAttr(span, 'http.target', request.url);
|
|
156
|
+
setAttr(span, 'http.host', safeHostFromIncoming(request));
|
|
157
|
+
} else {
|
|
158
|
+
// Outgoing request (CLIENT)
|
|
159
|
+
setAttr(span, 'http.method', request?.method);
|
|
160
|
+
const url = safeUrlFromOutgoingRequest(request);
|
|
161
|
+
if (url) setAttr(span, 'http.url', url);
|
|
162
|
+
const host = request?.hostname || (request?.host ? String(request.host).split(':')[0] : '');
|
|
163
|
+
if (host) setAttr(span, 'net.peer.name', host);
|
|
164
|
+
if (request?.port) setAttrNumber(span, 'net.peer.port', request.port);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (response && typeof response === 'object' && 'statusCode' in response) {
|
|
168
|
+
setAttrNumber(span, 'http.status_code', response.statusCode);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const expressInstrumentation = new ExpressInstrumentation();
|
|
174
|
+
const mongoInstrumentation = new MongoDBInstrumentation();
|
|
175
|
+
const mongooseInstrumentation = new MongooseInstrumentation();
|
|
176
|
+
|
|
177
|
+
sdk = new NodeSDK({
|
|
178
|
+
resource,
|
|
179
|
+
// Important: keep span recording ON so APM metrics derived from spans are not undersampled.
|
|
180
|
+
// Raw trace export sampling is handled inside SentienGuardTraceSpanExporter instead.
|
|
181
|
+
sampler: new AlwaysOnSampler(),
|
|
182
|
+
textMapPropagator: new W3CTraceContextPropagator(),
|
|
183
|
+
// Include MongoDB/Mongoose instrumentations for "zero-code" DB spans.
|
|
184
|
+
// These are safe even when the app doesn't use MongoDB; they patch when modules are loaded.
|
|
185
|
+
instrumentations: [httpInstrumentation, expressInstrumentation, mongoInstrumentation, mongooseInstrumentation],
|
|
186
|
+
spanProcessors: [
|
|
187
|
+
new BatchSpanProcessor(metricsExporter),
|
|
188
|
+
new BatchSpanProcessor(traceExporter)
|
|
189
|
+
],
|
|
190
|
+
autoDetectResources: false
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
sdk.start();
|
|
194
|
+
tracingActive = true;
|
|
195
|
+
debug('OpenTelemetry tracing started (W3C Trace Context + HTTP/Express)');
|
|
196
|
+
return true;
|
|
197
|
+
} catch (err) {
|
|
198
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
199
|
+
debug(`OpenTelemetry failed to start, falling back to legacy instrumentation: ${msg}`);
|
|
200
|
+
sdk = null;
|
|
201
|
+
tracingActive = false;
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function isTracingActive() {
|
|
207
|
+
return tracingActive;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Hex trace id for the active span (log correlation); undefined if none. */
|
|
211
|
+
export function getActiveTraceId() {
|
|
212
|
+
const s = trace.getActiveSpan();
|
|
213
|
+
if (!s) return undefined;
|
|
214
|
+
const c = s.spanContext();
|
|
215
|
+
return c.traceId && c.traceFlags !== undefined ? c.traceId : undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function shutdownTracing() {
|
|
219
|
+
if (!sdk) return;
|
|
220
|
+
try {
|
|
221
|
+
await sdk.shutdown();
|
|
222
|
+
debug('OpenTelemetry tracing shut down');
|
|
223
|
+
} catch (err) {
|
|
224
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
225
|
+
debug(`OpenTelemetry shutdown error: ${msg}`);
|
|
226
|
+
} finally {
|
|
227
|
+
sdk = null;
|
|
228
|
+
tracingActive = false;
|
|
229
|
+
}
|
|
230
|
+
}
|