@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/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; // ms
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; // Relative to trace start
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; // Normalized
22
- path: string; // Raw
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 / Connect
15
+ // Express
14
16
  requestHandler: expressMiddleware,
17
+ errorHandler: expressErrorHandler,
15
18
 
16
- // Next.js
17
- wrapNextRoute, // For App Router (Route Handlers)
18
- wrapNextPages, // For Pages Router (API Routes)
19
+ // Next
20
+ wrapNextRoute,
21
+ wrapNextPages,
19
22
 
20
- // H3 / Nuxt / Nitro
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
  };
@@ -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
- // Hook: On Request (Start Timer)
10
+ // 1. Start Trace
13
11
  fastify.addHook('onRequest', (request: any, reply: any, next: Function) => {
14
- request.senzorStart = performance.now();
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({
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
 
@@ -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;
@@ -1,16 +1,14 @@
1
1
  import { client } from '../core/client';
2
2
  import { normalizePath } from '../core/normalizer';
3
3
 
4
- // --- App Router Wrapper (Route Handlers) ---
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 (API Routes) ---
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); // Fallback if finish doesn't fire
50
+ res.once('close', done);
54
51
 
55
- // 3. Execute
56
52
  try {
57
53
  return await handler(req, res);
58
- } catch (e) {
59
- // Next.js Pages router usually handles errors internally,
60
- // but we ensure we catch sync errors here
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
  });