@senzops/apm-node 1.1.3 → 1.1.4
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 +33 -11
- package/src/core/context.ts +13 -6
- package/src/core/types.ts +12 -5
- package/src/index.ts +10 -7
- package/src/instrumentation/http.ts +1 -1
- package/src/middleware/express.ts +17 -4
- package/src/wrappers/fastify.ts +15 -19
- package/src/wrappers/h3.ts +3 -5
- package/src/wrappers/next.ts +14 -16
package/src/core/types.ts
CHANGED
|
@@ -2,36 +2,43 @@ 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
10
|
name: string;
|
|
11
11
|
type: 'db' | 'http' | 'function' | 'custom';
|
|
12
|
-
startTime: number;
|
|
12
|
+
startTime: number;
|
|
13
13
|
duration: number;
|
|
14
14
|
status?: number;
|
|
15
15
|
meta?: Record<string, any>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export interface TraceError {
|
|
19
|
+
name: string;
|
|
20
|
+
message: string;
|
|
21
|
+
stack?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
export interface Trace {
|
|
19
25
|
traceId: string;
|
|
20
26
|
method: string;
|
|
21
|
-
route: string;
|
|
22
|
-
path: string;
|
|
27
|
+
route: string;
|
|
28
|
+
path: string;
|
|
23
29
|
status: number;
|
|
24
30
|
duration: number;
|
|
25
31
|
ip?: string;
|
|
26
32
|
userAgent?: string;
|
|
27
33
|
timestamp: string;
|
|
28
34
|
spans: Span[];
|
|
35
|
+
error?: TraceError;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
// Internal interface for an active trace object
|
|
32
38
|
export interface ActiveTrace {
|
|
33
39
|
id: string;
|
|
34
40
|
startTime: number;
|
|
35
41
|
data: Partial<Trace>;
|
|
36
42
|
spans: Span[];
|
|
43
|
+
error?: TraceError;
|
|
37
44
|
}
|
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
|
|
@@ -122,7 +122,7 @@ export const instrumentHttp = (ingestUrl: string, debug = false) => {
|
|
|
122
122
|
startTime,
|
|
123
123
|
duration,
|
|
124
124
|
status: error ? 500 : res?.statusCode || 0,
|
|
125
|
-
meta: { url: urlStr, method, library: 'http' }
|
|
125
|
+
meta: { url: urlStr, method, library: 'http', error: error ? error.message : undefined }
|
|
126
126
|
});
|
|
127
127
|
};
|
|
128
128
|
|
|
@@ -1,8 +1,8 @@
|
|
|
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,
|
|
@@ -10,9 +10,11 @@ export const expressMiddleware = () => {
|
|
|
10
10
|
userAgent: req.headers['user-agent'],
|
|
11
11
|
}, () => {
|
|
12
12
|
|
|
13
|
+
// Auto-detect status code on finish
|
|
13
14
|
res.once('finish', () => {
|
|
14
15
|
try {
|
|
15
16
|
let route = 'UNKNOWN';
|
|
17
|
+
// Express populates req.route only if a route matched
|
|
16
18
|
if (req.route && req.route.path) {
|
|
17
19
|
route = (req.baseUrl || '') + req.route.path;
|
|
18
20
|
} else if (res.statusCode === 404) {
|
|
@@ -22,12 +24,23 @@ export const expressMiddleware = () => {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
client.endTrace(res.statusCode, { route });
|
|
25
|
-
} catch (e) {
|
|
26
|
-
// Fail open
|
|
27
|
-
}
|
|
27
|
+
} catch (e) { /* Fail open */ }
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
next();
|
|
31
31
|
});
|
|
32
32
|
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// 2. Error Handler (Place after routes)
|
|
36
|
+
// This is required in Express to capture the actual Error Object (Stack Trace)
|
|
37
|
+
export const expressErrorHandler = () => {
|
|
38
|
+
return (err: any, req: any, res: any, next: (err?: any) => void) => {
|
|
39
|
+
|
|
40
|
+
// 1. Capture the exception context
|
|
41
|
+
client.captureError(err);
|
|
42
|
+
|
|
43
|
+
// 2. Pass it to the next error handler (don't swallow it)
|
|
44
|
+
next(err);
|
|
45
|
+
};
|
|
33
46
|
};
|
package/src/wrappers/fastify.ts
CHANGED
|
@@ -1,37 +1,33 @@
|
|
|
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
5
|
|
|
7
|
-
// Init if options provided inline, otherwise assume global init
|
|
8
6
|
if (options && options.apiKey) {
|
|
9
7
|
client.init(options);
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
//
|
|
10
|
+
// 1. Start Trace
|
|
13
11
|
fastify.addHook('onRequest', (request: any, reply: any, next: Function) => {
|
|
14
|
-
|
|
15
|
-
|
|
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({
|
|
12
|
+
// FIX: Wrap next in a closure to satisfy TS types
|
|
13
|
+
client.startTrace({
|
|
26
14
|
method: request.method,
|
|
27
|
-
route: route || 'UNKNOWN',
|
|
28
15
|
path: request.raw.url || request.url,
|
|
29
|
-
status: reply.statusCode,
|
|
30
|
-
duration: duration,
|
|
31
16
|
ip: request.ip,
|
|
32
17
|
userAgent: request.headers['user-agent']
|
|
33
|
-
});
|
|
18
|
+
}, () => next());
|
|
19
|
+
});
|
|
34
20
|
|
|
21
|
+
// 2. Capture Errors
|
|
22
|
+
fastify.addHook('onError', (request: any, reply: any, error: any, next: Function) => {
|
|
23
|
+
client.captureError(error);
|
|
24
|
+
next();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// 3. End Trace
|
|
28
|
+
fastify.addHook('onResponse', (request: any, reply: any, next: Function) => {
|
|
29
|
+
const route = request.routeOptions?.url || request.routerPath || 'UNKNOWN';
|
|
30
|
+
client.endTrace(reply.statusCode, { route });
|
|
35
31
|
next();
|
|
36
32
|
});
|
|
37
33
|
|
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,7 +8,6 @@ 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,
|
|
@@ -18,16 +16,16 @@ export const wrapH3 = (handler: EventHandler) => {
|
|
|
18
16
|
}, async () => {
|
|
19
17
|
try {
|
|
20
18
|
const response = await handler(event);
|
|
21
|
-
|
|
22
|
-
// H3/Nitro response status
|
|
23
19
|
let status = 200;
|
|
24
20
|
if (event.node.res.statusCode) status = event.node.res.statusCode;
|
|
25
|
-
// Check if response is an error object
|
|
26
21
|
if (response && response.statusCode) status = response.statusCode;
|
|
27
22
|
|
|
28
23
|
client.endTrace(status, { route: getRoute(event, path) });
|
|
29
24
|
return response;
|
|
30
25
|
} catch (err: any) {
|
|
26
|
+
// AUTOMATIC ERROR CAPTURE
|
|
27
|
+
client.captureError(err);
|
|
28
|
+
|
|
31
29
|
const status = err.statusCode || err.status || 500;
|
|
32
30
|
client.endTrace(status, { route: getRoute(event, path) });
|
|
33
31
|
throw err;
|
package/src/wrappers/next.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
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
|
-
// 1. Extract Info
|
|
8
7
|
const url = req.url ? new URL(req.url) : { pathname: '/' };
|
|
9
8
|
const method = req.method || 'GET';
|
|
10
9
|
const ua = req.headers.get ? req.headers.get('user-agent') : undefined;
|
|
11
10
|
const ip = req.headers.get ? req.headers.get('x-forwarded-for') : undefined;
|
|
12
11
|
|
|
13
|
-
// 2. Run in Context
|
|
14
12
|
return client.startTrace({
|
|
15
13
|
method,
|
|
16
14
|
path: url.pathname,
|
|
@@ -20,44 +18,44 @@ export const wrapNextRoute = (handler: Function) => {
|
|
|
20
18
|
try {
|
|
21
19
|
const response = await handler(req, context);
|
|
22
20
|
const status = response?.status || 200;
|
|
23
|
-
|
|
24
21
|
client.endTrace(status, { route: normalizePath(url.pathname) });
|
|
25
22
|
return response;
|
|
26
23
|
} catch (err: any) {
|
|
24
|
+
// AUTOMATIC ERROR CAPTURE
|
|
25
|
+
client.captureError(err);
|
|
27
26
|
client.endTrace(500, { route: normalizePath(url.pathname) });
|
|
28
|
-
throw err;
|
|
27
|
+
throw err; // Re-throw so Next.js handles the error page
|
|
29
28
|
}
|
|
30
29
|
});
|
|
31
30
|
};
|
|
32
31
|
};
|
|
33
32
|
|
|
34
|
-
// --- Pages Router Wrapper
|
|
33
|
+
// --- Pages Router Wrapper ---
|
|
35
34
|
export const wrapNextPages = (handler: Function) => {
|
|
36
35
|
return async (req: any, res: any) => {
|
|
37
36
|
const path = req.url ? req.url.split('?')[0] : '/';
|
|
38
|
-
|
|
39
|
-
// 1. Run in Context
|
|
37
|
+
|
|
40
38
|
return client.startTrace({
|
|
41
39
|
method: req.method || 'GET',
|
|
42
40
|
path: path,
|
|
43
41
|
userAgent: req.headers['user-agent'],
|
|
44
42
|
ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,
|
|
45
43
|
}, async () => {
|
|
46
|
-
|
|
47
|
-
// 2. Hook Response
|
|
44
|
+
|
|
48
45
|
const done = () => {
|
|
49
46
|
client.endTrace(res.statusCode || 200, { route: normalizePath(path) });
|
|
50
47
|
};
|
|
51
|
-
|
|
48
|
+
|
|
52
49
|
res.once('finish', done);
|
|
53
|
-
res.once('close', done);
|
|
50
|
+
res.once('close', done);
|
|
54
51
|
|
|
55
|
-
// 3. Execute
|
|
56
52
|
try {
|
|
57
53
|
return await handler(req, res);
|
|
58
|
-
} catch (e) {
|
|
59
|
-
//
|
|
60
|
-
|
|
54
|
+
} catch (e: any) {
|
|
55
|
+
// AUTOMATIC ERROR CAPTURE
|
|
56
|
+
client.captureError(e);
|
|
57
|
+
// Note: In Pages dir, we rely on 'finish' listener above to close trace,
|
|
58
|
+
// but capturing here ensures the error data is attached.
|
|
61
59
|
throw e;
|
|
62
60
|
}
|
|
63
61
|
});
|