@senzops/apm-node 1.1.14 → 1.1.16
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/CHANGELOG.md +33 -0
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/client.ts +161 -14
- package/src/instrumentation/http.ts +19 -41
- package/src/utils/sdkMeta.ts +6 -0
- package/src/utils/traceContext.ts +44 -0
- package/tsconfig.json +1 -0
|
@@ -3,6 +3,7 @@ import https from 'https';
|
|
|
3
3
|
import { URL } from 'url';
|
|
4
4
|
import { Context } from '../core/context';
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
|
+
import { generateTraceparent } from '../utils/traceContext';
|
|
6
7
|
|
|
7
8
|
const shimmer = (module: any, methodName: string, wrapper: (original: Function) => Function) => {
|
|
8
9
|
if (!module[methodName]) return;
|
|
@@ -10,6 +11,9 @@ const shimmer = (module: any, methodName: string, wrapper: (original: Function)
|
|
|
10
11
|
module[methodName] = wrapper(original);
|
|
11
12
|
};
|
|
12
13
|
|
|
14
|
+
// 16-char hex for W3C standard spans
|
|
15
|
+
const generateSpanId = () => randomUUID().replace(/-/g, '').slice(0, 16);
|
|
16
|
+
|
|
13
17
|
// --- FETCH INSTRUMENTATION ---
|
|
14
18
|
export const instrumentFetch = (ingestUrl: string, debug = false) => {
|
|
15
19
|
if (!globalThis.fetch) return;
|
|
@@ -38,16 +42,14 @@ export const instrumentFetch = (ingestUrl: string, debug = false) => {
|
|
|
38
42
|
const method = (init?.method || 'GET').toUpperCase();
|
|
39
43
|
const startTime = performance.now() - trace.startTime;
|
|
40
44
|
const spanStartAbs = performance.now();
|
|
41
|
-
const spanId =
|
|
45
|
+
const spanId = generateSpanId();
|
|
42
46
|
|
|
43
47
|
let hostname = 'unknown';
|
|
44
48
|
try { hostname = new URL(urlStr).hostname; } catch (e) { }
|
|
45
49
|
|
|
46
|
-
// Inject Headers
|
|
47
50
|
const newInit = { ...init } as RequestInit;
|
|
48
51
|
if (!newInit.headers) newInit.headers = {};
|
|
49
52
|
|
|
50
|
-
// Helper to set header on various types
|
|
51
53
|
const setHeader = (key: string, value: string) => {
|
|
52
54
|
if (newInit.headers instanceof Headers) {
|
|
53
55
|
newInit.headers.set(key, value);
|
|
@@ -58,35 +60,21 @@ export const instrumentFetch = (ingestUrl: string, debug = false) => {
|
|
|
58
60
|
}
|
|
59
61
|
};
|
|
60
62
|
|
|
63
|
+
// W3C Trace Context Injection
|
|
64
|
+
setHeader('traceparent', generateTraceparent(trace.id, spanId));
|
|
65
|
+
|
|
66
|
+
// Legacy fallback for older Senzor services
|
|
61
67
|
setHeader('x-senzor-trace-id', trace.id);
|
|
62
68
|
setHeader('x-senzor-parent-span-id', spanId);
|
|
63
69
|
|
|
64
70
|
try {
|
|
65
71
|
const response = await originalFetch(input, newInit);
|
|
66
|
-
|
|
67
72
|
const duration = performance.now() - spanStartAbs;
|
|
68
|
-
Context.addSpan({
|
|
69
|
-
spanId,
|
|
70
|
-
name: `${method} ${hostname}`,
|
|
71
|
-
type: 'http',
|
|
72
|
-
startTime,
|
|
73
|
-
duration,
|
|
74
|
-
status: response.status,
|
|
75
|
-
meta: { url: urlStr, method, library: 'fetch' }
|
|
76
|
-
});
|
|
77
|
-
|
|
73
|
+
Context.addSpan({ spanId, name: `${method} ${hostname}`, type: 'http', startTime, duration, status: response.status, meta: { url: urlStr, method, library: 'fetch' } });
|
|
78
74
|
return response;
|
|
79
75
|
} catch (err: any) {
|
|
80
76
|
const duration = performance.now() - spanStartAbs;
|
|
81
|
-
Context.addSpan({
|
|
82
|
-
spanId,
|
|
83
|
-
name: `${method} ${hostname}`,
|
|
84
|
-
type: 'http',
|
|
85
|
-
startTime,
|
|
86
|
-
duration,
|
|
87
|
-
status: 500,
|
|
88
|
-
meta: { error: err.message, url: urlStr, library: 'fetch' }
|
|
89
|
-
});
|
|
77
|
+
Context.addSpan({ spanId, name: `${method} ${hostname}`, type: 'http', startTime, duration, status: 500, meta: { error: err.message, url: urlStr, library: 'fetch' } });
|
|
90
78
|
throw err;
|
|
91
79
|
}
|
|
92
80
|
};
|
|
@@ -103,7 +91,6 @@ export const instrumentHttp = (ingestUrl: string, debug = false) => {
|
|
|
103
91
|
let urlStr = '';
|
|
104
92
|
let optionsIndex = 0;
|
|
105
93
|
|
|
106
|
-
// Parsing Logic: http.request(url, options, cb) OR http.request(options, cb)
|
|
107
94
|
if (typeof args[0] === 'string' || args[0] instanceof URL) {
|
|
108
95
|
urlStr = args[0].toString();
|
|
109
96
|
optionsIndex = 1;
|
|
@@ -111,13 +98,11 @@ export const instrumentHttp = (ingestUrl: string, debug = false) => {
|
|
|
111
98
|
optionsIndex = 0;
|
|
112
99
|
}
|
|
113
100
|
|
|
114
|
-
// Ensure options object exists at correct index
|
|
115
101
|
if (!args[optionsIndex] || typeof args[optionsIndex] !== 'object') {
|
|
116
102
|
args[optionsIndex] = {};
|
|
117
103
|
}
|
|
118
104
|
options = args[optionsIndex];
|
|
119
105
|
|
|
120
|
-
// Construct URL if missing
|
|
121
106
|
if (!urlStr) {
|
|
122
107
|
const protocol = options.protocol || (options.port === 443 ? 'https:' : 'http:');
|
|
123
108
|
const host = options.hostname || options.host || 'localhost';
|
|
@@ -125,7 +110,6 @@ export const instrumentHttp = (ingestUrl: string, debug = false) => {
|
|
|
125
110
|
urlStr = `${protocol}//${host}${path}`;
|
|
126
111
|
}
|
|
127
112
|
|
|
128
|
-
// Guard
|
|
129
113
|
if (ingestHost && (urlStr.includes(ingestHost) || (options.hostname && options.hostname.includes(ingestHost)))) {
|
|
130
114
|
return original.apply(this, args);
|
|
131
115
|
}
|
|
@@ -136,33 +120,27 @@ export const instrumentHttp = (ingestUrl: string, debug = false) => {
|
|
|
136
120
|
const method = (options.method || 'GET').toUpperCase();
|
|
137
121
|
const startTime = performance.now() - trace.startTime;
|
|
138
122
|
const spanStartAbs = performance.now();
|
|
139
|
-
const spanId =
|
|
123
|
+
const spanId = generateSpanId();
|
|
140
124
|
|
|
141
125
|
let hostname = 'unknown';
|
|
142
126
|
try { hostname = new URL(urlStr).hostname; } catch (e) { hostname = options.hostname || 'unknown'; }
|
|
143
127
|
|
|
144
|
-
// Inject Headers (Mutate the options object reference directly)
|
|
145
128
|
if (!options.headers) options.headers = {};
|
|
129
|
+
|
|
130
|
+
// W3C Trace Context Injection
|
|
131
|
+
options.headers['traceparent'] = generateTraceparent(trace.id, spanId);
|
|
132
|
+
|
|
133
|
+
// Legacy fallback
|
|
146
134
|
options.headers['x-senzor-trace-id'] = trace.id;
|
|
147
135
|
options.headers['x-senzor-parent-span-id'] = spanId;
|
|
148
136
|
|
|
149
|
-
|
|
150
|
-
if (debug) console.log(`[Senzor] Injecting headers to ${urlStr}`);
|
|
137
|
+
if (debug) console.log(`[Senzor] Injecting W3C traceparent headers to ${urlStr}`);
|
|
151
138
|
|
|
152
|
-
// Call Original
|
|
153
139
|
const req = original.apply(this, args);
|
|
154
140
|
|
|
155
141
|
const captureSpan = (res: any, error?: Error) => {
|
|
156
142
|
const duration = performance.now() - spanStartAbs;
|
|
157
|
-
Context.addSpan({
|
|
158
|
-
spanId,
|
|
159
|
-
name: `${method} ${hostname}`,
|
|
160
|
-
type: 'http',
|
|
161
|
-
startTime,
|
|
162
|
-
duration,
|
|
163
|
-
status: error ? 500 : res?.statusCode || 0,
|
|
164
|
-
meta: { url: urlStr, method, library: 'http' }
|
|
165
|
-
});
|
|
143
|
+
Context.addSpan({ spanId, name: `${method} ${hostname}`, type: 'http', startTime, duration, status: error ? 500 : res?.statusCode || 0, meta: { url: urlStr, method, library: 'http' } });
|
|
166
144
|
};
|
|
167
145
|
|
|
168
146
|
req.on('response', (res: any) => {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3C Trace Context Implementation
|
|
3
|
+
* Standard: https://www.w3.org/TR/trace-context/
|
|
4
|
+
* Format: 00-{traceId}-{spanId}-{traceFlags}
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface TraceContext {
|
|
8
|
+
traceId: string;
|
|
9
|
+
parentSpanId: string;
|
|
10
|
+
sampled: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
14
|
+
|
|
15
|
+
export const parseTraceparent = (header?: string | string[]): TraceContext | null => {
|
|
16
|
+
if (!header) return null;
|
|
17
|
+
|
|
18
|
+
const traceparent = Array.isArray(header) ? header[0] : header;
|
|
19
|
+
if (typeof traceparent !== 'string') return null;
|
|
20
|
+
|
|
21
|
+
const match = traceparent.trim().toLowerCase().match(TRACEPARENT_REGEX);
|
|
22
|
+
if (!match) return null;
|
|
23
|
+
|
|
24
|
+
const traceId = match[1];
|
|
25
|
+
const parentSpanId = match[2];
|
|
26
|
+
const flags = match[3];
|
|
27
|
+
|
|
28
|
+
// Invalid IDs according to W3C specification
|
|
29
|
+
if (traceId === '00000000000000000000000000000000') return null;
|
|
30
|
+
if (parentSpanId === '0000000000000000') return null;
|
|
31
|
+
|
|
32
|
+
// The least significant bit of flags indicates if the trace is sampled
|
|
33
|
+
const sampled = (parseInt(flags, 16) & 0x01) === 0x01;
|
|
34
|
+
|
|
35
|
+
return { traceId, parentSpanId, sampled };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generates a valid W3C traceparent header string for OUTGOING requests.
|
|
40
|
+
*/
|
|
41
|
+
export const generateTraceparent = (traceId: string, spanId: string, sampled: boolean = true): string => {
|
|
42
|
+
const flags = sampled ? '01' : '00';
|
|
43
|
+
return `00-${traceId}-${spanId}-${flags}`;
|
|
44
|
+
};
|