@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.
@@ -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 = randomUUID();
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 = randomUUID();
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
- // Debug
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,6 @@
1
+ import pkg from '../../package.json';
2
+
3
+ export const SDK_META = {
4
+ name: pkg.name,
5
+ version: pkg.version
6
+ };
@@ -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
+ };
package/tsconfig.json CHANGED
@@ -7,6 +7,7 @@
7
7
  "strict": true,
8
8
  "esModuleInterop": true,
9
9
  "skipLibCheck": true,
10
+ "resolveJsonModule": true,
10
11
  "forceConsistentCasingInFileNames": true
11
12
  },
12
13
  "include": ["src/**/*"],