@senzops/apm-node 1.1.3 → 1.1.5
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/README.md +26 -34
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -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 +57 -13
- package/src/core/context.ts +13 -6
- package/src/core/types.ts +18 -5
- package/src/index.ts +10 -7
- package/src/instrumentation/http.ts +43 -10
- package/src/middleware/express.ts +18 -4
- package/src/wrappers/fastify.ts +13 -21
- package/src/wrappers/h3.ts +2 -5
- package/src/wrappers/next.ts +36 -18
package/src/core/context.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
-
import { ActiveTrace } from './types';
|
|
2
|
+
import { ActiveTrace, TraceError } from './types';
|
|
3
3
|
|
|
4
4
|
export const storage = new AsyncLocalStorage<ActiveTrace>();
|
|
5
5
|
|
|
@@ -16,11 +16,18 @@ export const Context = {
|
|
|
16
16
|
const store = storage.getStore();
|
|
17
17
|
if (store) {
|
|
18
18
|
store.spans.push(span);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// Attach error to current trace
|
|
23
|
+
setError: (error: Error) => {
|
|
24
|
+
const store = storage.getStore();
|
|
25
|
+
if (store) {
|
|
26
|
+
store.error = {
|
|
27
|
+
name: error.name,
|
|
28
|
+
message: error.message,
|
|
29
|
+
stack: error.stack
|
|
30
|
+
};
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
};
|
package/src/core/types.ts
CHANGED
|
@@ -2,36 +2,49 @@ export interface SenzorOptions {
|
|
|
2
2
|
apiKey: string;
|
|
3
3
|
endpoint?: string;
|
|
4
4
|
batchSize?: number;
|
|
5
|
-
flushInterval?: number;
|
|
5
|
+
flushInterval?: number;
|
|
6
6
|
debug?: boolean;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface Span {
|
|
10
|
+
spanId: string; // NEW: Unique ID for this span
|
|
10
11
|
name: string;
|
|
11
12
|
type: 'db' | 'http' | 'function' | 'custom';
|
|
12
|
-
startTime: number;
|
|
13
|
+
startTime: number;
|
|
13
14
|
duration: number;
|
|
14
15
|
status?: number;
|
|
15
16
|
meta?: Record<string, any>;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
export interface TraceError {
|
|
20
|
+
name: string;
|
|
21
|
+
message: string;
|
|
22
|
+
stack?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
export interface Trace {
|
|
19
26
|
traceId: string;
|
|
27
|
+
|
|
28
|
+
// Distributed Tracing Fields
|
|
29
|
+
parentTraceId?: string; // NEW
|
|
30
|
+
parentSpanId?: string; // NEW
|
|
31
|
+
|
|
20
32
|
method: string;
|
|
21
|
-
route: string;
|
|
22
|
-
path: string;
|
|
33
|
+
route: string;
|
|
34
|
+
path: string;
|
|
23
35
|
status: number;
|
|
24
36
|
duration: number;
|
|
25
37
|
ip?: string;
|
|
26
38
|
userAgent?: string;
|
|
27
39
|
timestamp: string;
|
|
28
40
|
spans: Span[];
|
|
41
|
+
error?: TraceError;
|
|
29
42
|
}
|
|
30
43
|
|
|
31
|
-
// Internal interface for an active trace object
|
|
32
44
|
export interface ActiveTrace {
|
|
33
45
|
id: string;
|
|
34
46
|
startTime: number;
|
|
35
47
|
data: Partial<Trace>;
|
|
36
48
|
spans: Span[];
|
|
49
|
+
error?: TraceError;
|
|
37
50
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { client } from './core/client';
|
|
2
|
-
import { expressMiddleware } from './middleware/express';
|
|
2
|
+
import { expressMiddleware, expressErrorHandler } from './middleware/express';
|
|
3
3
|
import { wrapH3 } from './wrappers/h3';
|
|
4
4
|
import { wrapNextRoute, wrapNextPages } from './wrappers/next';
|
|
5
5
|
import { senzorPlugin } from './wrappers/fastify';
|
|
6
6
|
import { SenzorOptions } from './core/types';
|
|
7
7
|
|
|
8
8
|
const Senzor = {
|
|
9
|
-
// Core
|
|
10
9
|
init: (options: SenzorOptions) => client.init(options),
|
|
11
10
|
flush: () => client.flush(),
|
|
11
|
+
track: client.track.bind(client),
|
|
12
|
+
startSpan: client.startSpan.bind(client),
|
|
13
|
+
captureException: client.captureError.bind(client),
|
|
12
14
|
|
|
13
|
-
// Express
|
|
15
|
+
// Express
|
|
14
16
|
requestHandler: expressMiddleware,
|
|
17
|
+
errorHandler: expressErrorHandler,
|
|
15
18
|
|
|
16
|
-
// Next
|
|
17
|
-
wrapNextRoute,
|
|
18
|
-
wrapNextPages,
|
|
19
|
+
// Next
|
|
20
|
+
wrapNextRoute,
|
|
21
|
+
wrapNextPages,
|
|
19
22
|
|
|
20
|
-
// H3
|
|
23
|
+
// H3
|
|
21
24
|
wrapH3,
|
|
22
25
|
|
|
23
26
|
// Fastify
|
|
@@ -2,6 +2,7 @@ import http from 'http';
|
|
|
2
2
|
import https from 'https';
|
|
3
3
|
import { URL } from 'url';
|
|
4
4
|
import { Context } from '../core/context';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
5
6
|
|
|
6
7
|
const shimmer = (module: any, methodName: string, wrapper: (original: Function) => Function) => {
|
|
7
8
|
if (!module[methodName]) return;
|
|
@@ -9,7 +10,7 @@ const shimmer = (module: any, methodName: string, wrapper: (original: Function)
|
|
|
9
10
|
module[methodName] = wrapper(original);
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
// ---
|
|
13
|
+
// --- FETCH INSTRUMENTATION (Node 18+ / Edge) ---
|
|
13
14
|
export const instrumentFetch = (ingestUrl: string, debug = false) => {
|
|
14
15
|
if (!globalThis.fetch) return;
|
|
15
16
|
|
|
@@ -24,7 +25,7 @@ export const instrumentFetch = (ingestUrl: string, debug = false) => {
|
|
|
24
25
|
let urlStr = '';
|
|
25
26
|
if (typeof input === 'string') urlStr = input;
|
|
26
27
|
else if (input instanceof URL) urlStr = input.toString();
|
|
27
|
-
else if (input && input.url) urlStr = input.url;
|
|
28
|
+
else if (input && (input as any).url) urlStr = (input as any).url;
|
|
28
29
|
|
|
29
30
|
// 2. Infinite Loop Guard
|
|
30
31
|
if (ingestHost && urlStr.includes(ingestHost)) {
|
|
@@ -37,21 +38,45 @@ export const instrumentFetch = (ingestUrl: string, debug = false) => {
|
|
|
37
38
|
return originalFetch(input, init);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
// 4.
|
|
41
|
+
// 4. Prepare Metadata & ID
|
|
41
42
|
const method = (init?.method || 'GET').toUpperCase();
|
|
42
43
|
const startTime = performance.now() - trace.startTime;
|
|
43
44
|
const spanStartAbs = performance.now();
|
|
45
|
+
const spanId = randomUUID(); // New ID for this specific outbound call
|
|
46
|
+
|
|
44
47
|
let hostname = 'unknown';
|
|
45
48
|
try { hostname = new URL(urlStr).hostname; } catch (e) { }
|
|
46
49
|
|
|
47
|
-
if (debug) console.log(`[Senzor]
|
|
50
|
+
if (debug) console.log(`[Senzor] Fetch: ${method} ${hostname}`);
|
|
51
|
+
|
|
52
|
+
// 5. Inject Distributed Tracing Headers
|
|
53
|
+
// We need to clone init or create it to avoid mutating original ref unexpectedly
|
|
54
|
+
const newInit = { ...init };
|
|
55
|
+
if (!newInit.headers) {
|
|
56
|
+
newInit.headers = {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Handle different Header formats (Headers object vs plain object)
|
|
60
|
+
if (newInit.headers instanceof Headers) {
|
|
61
|
+
newInit.headers.set('x-senzor-trace-id', trace.id);
|
|
62
|
+
newInit.headers.set('x-senzor-parent-span-id', spanId);
|
|
63
|
+
} else if (Array.isArray(newInit.headers)) {
|
|
64
|
+
newInit.headers.push(['x-senzor-trace-id', trace.id]);
|
|
65
|
+
newInit.headers.push(['x-senzor-parent-span-id', spanId]);
|
|
66
|
+
} else {
|
|
67
|
+
// Plain object
|
|
68
|
+
(newInit.headers as any)['x-senzor-trace-id'] = trace.id;
|
|
69
|
+
(newInit.headers as any)['x-senzor-parent-span-id'] = spanId;
|
|
70
|
+
}
|
|
48
71
|
|
|
49
72
|
try {
|
|
50
|
-
|
|
73
|
+
// 6. Execute Fetch
|
|
74
|
+
const response = await originalFetch(input, newInit);
|
|
51
75
|
|
|
52
|
-
//
|
|
76
|
+
// 7. Record Span
|
|
53
77
|
const duration = performance.now() - spanStartAbs;
|
|
54
78
|
Context.addSpan({
|
|
79
|
+
spanId,
|
|
55
80
|
name: `${method} ${hostname}`,
|
|
56
81
|
type: 'http',
|
|
57
82
|
startTime,
|
|
@@ -64,6 +89,7 @@ export const instrumentFetch = (ingestUrl: string, debug = false) => {
|
|
|
64
89
|
} catch (err: any) {
|
|
65
90
|
const duration = performance.now() - spanStartAbs;
|
|
66
91
|
Context.addSpan({
|
|
92
|
+
spanId,
|
|
67
93
|
name: `${method} ${hostname}`,
|
|
68
94
|
type: 'http',
|
|
69
95
|
startTime,
|
|
@@ -76,12 +102,10 @@ export const instrumentFetch = (ingestUrl: string, debug = false) => {
|
|
|
76
102
|
};
|
|
77
103
|
};
|
|
78
104
|
|
|
79
|
-
// ---
|
|
105
|
+
// --- HTTP/HTTPS INSTRUMENTATION ---
|
|
80
106
|
export const instrumentHttp = (ingestUrl: string, debug = false) => {
|
|
81
107
|
let ingestHost = '';
|
|
82
|
-
try {
|
|
83
|
-
ingestHost = new URL(ingestUrl).hostname;
|
|
84
|
-
} catch (e) { }
|
|
108
|
+
try { ingestHost = new URL(ingestUrl).hostname; } catch (e) { }
|
|
85
109
|
|
|
86
110
|
const requestWrapper = (original: Function) => {
|
|
87
111
|
return function (this: any, ...args: any[]) {
|
|
@@ -109,14 +133,22 @@ export const instrumentHttp = (ingestUrl: string, debug = false) => {
|
|
|
109
133
|
const method = (options.method || 'GET').toUpperCase();
|
|
110
134
|
const startTime = performance.now() - trace.startTime;
|
|
111
135
|
const spanStartAbs = performance.now();
|
|
136
|
+
const spanId = randomUUID(); // Generate ID
|
|
137
|
+
|
|
112
138
|
let hostname = 'unknown';
|
|
113
139
|
try { hostname = new URL(urlStr).hostname; } catch (e) { hostname = options.hostname || 'unknown'; }
|
|
114
140
|
|
|
141
|
+
// Inject Headers
|
|
142
|
+
if (!options.headers) options.headers = {};
|
|
143
|
+
options.headers['x-senzor-trace-id'] = trace.id;
|
|
144
|
+
options.headers['x-senzor-parent-span-id'] = spanId;
|
|
145
|
+
|
|
115
146
|
const req = original.apply(this, args);
|
|
116
147
|
|
|
117
148
|
const captureSpan = (res: any, error?: Error) => {
|
|
118
149
|
const duration = performance.now() - spanStartAbs;
|
|
119
150
|
Context.addSpan({
|
|
151
|
+
spanId,
|
|
120
152
|
name: `${method} ${hostname}`,
|
|
121
153
|
type: 'http',
|
|
122
154
|
startTime,
|
|
@@ -128,6 +160,7 @@ export const instrumentHttp = (ingestUrl: string, debug = false) => {
|
|
|
128
160
|
|
|
129
161
|
req.on('response', (res: any) => {
|
|
130
162
|
res.once('end', () => captureSpan(res));
|
|
163
|
+
res.once('close', () => captureSpan(res)); // Safety if stream not consumed
|
|
131
164
|
res.once('error', (err: Error) => captureSpan(res, err));
|
|
132
165
|
});
|
|
133
166
|
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { client } from '../core/client';
|
|
2
2
|
|
|
3
|
+
// 1. Request Handler (Place before routes)
|
|
3
4
|
export const expressMiddleware = () => {
|
|
4
5
|
return (req: any, res: any, next: () => void) => {
|
|
5
|
-
// We MUST use startTrace to enable Auto-Instrumentation for this request
|
|
6
6
|
client.startTrace({
|
|
7
7
|
method: req.method,
|
|
8
8
|
path: req.originalUrl || req.url,
|
|
9
9
|
ip: req.ip || req.socket?.remoteAddress,
|
|
10
10
|
userAgent: req.headers['user-agent'],
|
|
11
|
+
headers: req.headers
|
|
11
12
|
}, () => {
|
|
12
13
|
|
|
14
|
+
// Auto-detect status code on finish
|
|
13
15
|
res.once('finish', () => {
|
|
14
16
|
try {
|
|
15
17
|
let route = 'UNKNOWN';
|
|
18
|
+
// Express populates req.route only if a route matched
|
|
16
19
|
if (req.route && req.route.path) {
|
|
17
20
|
route = (req.baseUrl || '') + req.route.path;
|
|
18
21
|
} else if (res.statusCode === 404) {
|
|
@@ -22,12 +25,23 @@ export const expressMiddleware = () => {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
client.endTrace(res.statusCode, { route });
|
|
25
|
-
} catch (e) {
|
|
26
|
-
// Fail open
|
|
27
|
-
}
|
|
28
|
+
} catch (e) { /* Fail open */ }
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
next();
|
|
31
32
|
});
|
|
32
33
|
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// 2. Error Handler (Place after routes)
|
|
37
|
+
// This is required in Express to capture the actual Error Object (Stack Trace)
|
|
38
|
+
export const expressErrorHandler = () => {
|
|
39
|
+
return (err: any, req: any, res: any, next: (err?: any) => void) => {
|
|
40
|
+
|
|
41
|
+
// 1. Capture the exception context
|
|
42
|
+
client.captureError(err);
|
|
43
|
+
|
|
44
|
+
// 2. Pass it to the next error handler (don't swallow it)
|
|
45
|
+
next(err);
|
|
46
|
+
};
|
|
33
47
|
};
|
package/src/wrappers/fastify.ts
CHANGED
|
@@ -1,37 +1,29 @@
|
|
|
1
1
|
import { client } from '../core/client';
|
|
2
2
|
import { SenzorOptions } from '../core/types';
|
|
3
3
|
|
|
4
|
-
// We don't import Fastify types to keep zero-deps, but structure matches
|
|
5
4
|
export const senzorPlugin = (fastify: any, options: SenzorOptions, done: Function) => {
|
|
6
|
-
|
|
7
|
-
// Init if options provided inline, otherwise assume global init
|
|
8
5
|
if (options && options.apiKey) {
|
|
9
6
|
client.init(options);
|
|
10
7
|
}
|
|
11
8
|
|
|
12
|
-
// Hook: On Request (Start Timer)
|
|
13
9
|
fastify.addHook('onRequest', (request: any, reply: any, next: Function) => {
|
|
14
|
-
|
|
15
|
-
next();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Hook: On Response (End Timer & Track)
|
|
19
|
-
fastify.addHook('onResponse', (request: any, reply: any, next: Function) => {
|
|
20
|
-
const duration = performance.now() - (request.senzorStart || performance.now());
|
|
21
|
-
|
|
22
|
-
// Fastify provides 'routerPath' (e.g. /user/:id)
|
|
23
|
-
const route = request.routeOptions?.url || request.routerPath;
|
|
24
|
-
|
|
25
|
-
client.track({
|
|
10
|
+
client.startTrace({
|
|
26
11
|
method: request.method,
|
|
27
|
-
route: route || 'UNKNOWN',
|
|
28
12
|
path: request.raw.url || request.url,
|
|
29
|
-
status: reply.statusCode,
|
|
30
|
-
duration: duration,
|
|
31
13
|
ip: request.ip,
|
|
32
|
-
userAgent: request.headers['user-agent']
|
|
33
|
-
|
|
14
|
+
userAgent: request.headers['user-agent'],
|
|
15
|
+
headers: request.headers // Pass headers
|
|
16
|
+
}, () => next());
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
fastify.addHook('onError', (request: any, reply: any, error: any, next: Function) => {
|
|
20
|
+
client.captureError(error);
|
|
21
|
+
next();
|
|
22
|
+
});
|
|
34
23
|
|
|
24
|
+
fastify.addHook('onResponse', (request: any, reply: any, next: Function) => {
|
|
25
|
+
const route = request.routeOptions?.url || request.routerPath || 'UNKNOWN';
|
|
26
|
+
client.endTrace(reply.statusCode, { route });
|
|
35
27
|
next();
|
|
36
28
|
});
|
|
37
29
|
|
package/src/wrappers/h3.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { client } from '../core/client';
|
|
2
2
|
import { getRoute } from '../core/normalizer';
|
|
3
3
|
|
|
4
|
-
// Minimal types for H3 to avoid peer-deps
|
|
5
4
|
type EventHandler = (event: any) => any;
|
|
6
5
|
|
|
7
6
|
export const wrapH3 = (handler: EventHandler) => {
|
|
@@ -9,25 +8,23 @@ export const wrapH3 = (handler: EventHandler) => {
|
|
|
9
8
|
const req = event.node.req;
|
|
10
9
|
const path = req.originalUrl || req.url || '/';
|
|
11
10
|
|
|
12
|
-
// Start Trace Context
|
|
13
11
|
return client.startTrace({
|
|
14
12
|
method: req.method || 'GET',
|
|
15
13
|
path: path,
|
|
16
14
|
ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,
|
|
17
15
|
userAgent: req.headers['user-agent'],
|
|
16
|
+
headers: req.headers // Pass headers
|
|
18
17
|
}, async () => {
|
|
19
18
|
try {
|
|
20
19
|
const response = await handler(event);
|
|
21
|
-
|
|
22
|
-
// H3/Nitro response status
|
|
23
20
|
let status = 200;
|
|
24
21
|
if (event.node.res.statusCode) status = event.node.res.statusCode;
|
|
25
|
-
// Check if response is an error object
|
|
26
22
|
if (response && response.statusCode) status = response.statusCode;
|
|
27
23
|
|
|
28
24
|
client.endTrace(status, { route: getRoute(event, path) });
|
|
29
25
|
return response;
|
|
30
26
|
} catch (err: any) {
|
|
27
|
+
client.captureError(err);
|
|
31
28
|
const status = err.statusCode || err.status || 500;
|
|
32
29
|
client.endTrace(status, { route: getRoute(event, path) });
|
|
33
30
|
throw err;
|
package/src/wrappers/next.ts
CHANGED
|
@@ -1,29 +1,50 @@
|
|
|
1
1
|
import { client } from '../core/client';
|
|
2
2
|
import { normalizePath } from '../core/normalizer';
|
|
3
3
|
|
|
4
|
-
// --- App Router Wrapper
|
|
4
|
+
// --- App Router Wrapper ---
|
|
5
5
|
export const wrapNextRoute = (handler: Function) => {
|
|
6
6
|
return async (req: Request | any, context?: any) => {
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
// Extract info from Web Standard Request
|
|
8
9
|
const url = req.url ? new URL(req.url) : { pathname: '/' };
|
|
9
10
|
const method = req.method || 'GET';
|
|
10
|
-
const ua = req.headers.get ? req.headers.get('user-agent') : undefined;
|
|
11
|
-
const ip = req.headers.get ? req.headers.get('x-forwarded-for') : undefined;
|
|
12
11
|
|
|
13
|
-
//
|
|
12
|
+
// Header Extraction
|
|
13
|
+
let headers: Record<string, string> = {};
|
|
14
|
+
let ua: string | undefined;
|
|
15
|
+
let ip: string | undefined;
|
|
16
|
+
|
|
17
|
+
if (typeof req.headers.get === 'function') {
|
|
18
|
+
// It's a Web Request Object
|
|
19
|
+
ua = req.headers.get('user-agent');
|
|
20
|
+
ip = req.headers.get('x-forwarded-for');
|
|
21
|
+
|
|
22
|
+
// Convert to plain object for trace context extraction
|
|
23
|
+
req.headers.forEach((value: string, key: string) => {
|
|
24
|
+
headers[key] = value;
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
// It's a Node Request Object (rare in App router but possible)
|
|
28
|
+
headers = req.headers;
|
|
29
|
+
ua = headers['user-agent'];
|
|
30
|
+
ip = headers['x-forwarded-for'] as string;
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
return client.startTrace({
|
|
15
34
|
method,
|
|
16
35
|
path: url.pathname,
|
|
17
36
|
userAgent: ua,
|
|
18
|
-
ip: ip
|
|
37
|
+
ip: ip,
|
|
38
|
+
headers: headers // Pass extracted headers
|
|
19
39
|
}, async () => {
|
|
20
40
|
try {
|
|
21
41
|
const response = await handler(req, context);
|
|
22
42
|
const status = response?.status || 200;
|
|
23
|
-
|
|
43
|
+
|
|
24
44
|
client.endTrace(status, { route: normalizePath(url.pathname) });
|
|
25
45
|
return response;
|
|
26
46
|
} catch (err: any) {
|
|
47
|
+
client.captureError(err);
|
|
27
48
|
client.endTrace(500, { route: normalizePath(url.pathname) });
|
|
28
49
|
throw err;
|
|
29
50
|
}
|
|
@@ -31,33 +52,30 @@ export const wrapNextRoute = (handler: Function) => {
|
|
|
31
52
|
};
|
|
32
53
|
};
|
|
33
54
|
|
|
34
|
-
// --- Pages Router Wrapper
|
|
55
|
+
// --- Pages Router Wrapper ---
|
|
35
56
|
export const wrapNextPages = (handler: Function) => {
|
|
36
57
|
return async (req: any, res: any) => {
|
|
37
58
|
const path = req.url ? req.url.split('?')[0] : '/';
|
|
38
|
-
|
|
39
|
-
// 1. Run in Context
|
|
59
|
+
|
|
40
60
|
return client.startTrace({
|
|
41
61
|
method: req.method || 'GET',
|
|
42
62
|
path: path,
|
|
43
63
|
userAgent: req.headers['user-agent'],
|
|
44
64
|
ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,
|
|
65
|
+
headers: req.headers // Standard Node headers work fine
|
|
45
66
|
}, async () => {
|
|
46
|
-
|
|
47
|
-
// 2. Hook Response
|
|
67
|
+
|
|
48
68
|
const done = () => {
|
|
49
69
|
client.endTrace(res.statusCode || 200, { route: normalizePath(path) });
|
|
50
70
|
};
|
|
51
|
-
|
|
71
|
+
|
|
52
72
|
res.once('finish', done);
|
|
53
|
-
res.once('close', done);
|
|
73
|
+
res.once('close', done);
|
|
54
74
|
|
|
55
|
-
// 3. Execute
|
|
56
75
|
try {
|
|
57
76
|
return await handler(req, res);
|
|
58
|
-
} catch (e) {
|
|
59
|
-
|
|
60
|
-
// but we ensure we catch sync errors here
|
|
77
|
+
} catch (e: any) {
|
|
78
|
+
client.captureError(e);
|
|
61
79
|
throw e;
|
|
62
80
|
}
|
|
63
81
|
});
|