@senzops/apm-node 1.0.1 → 1.1.1

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.
@@ -1,74 +1,65 @@
1
1
  import { client } from '../core/client';
2
2
  import { normalizePath } from '../core/normalizer';
3
3
 
4
- // --- App Router Wrapper (GET, POST, etc.) ---
4
+ // --- App Router Wrapper (Route Handlers) ---
5
5
  export const wrapNextRoute = (handler: Function) => {
6
6
  return async (req: Request | any, context?: any) => {
7
- const start = performance.now();
8
- let status = 200;
7
+ // 1. Extract Info
8
+ const url = req.url ? new URL(req.url) : { pathname: '/' };
9
+ 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;
9
12
 
10
- try {
11
- const response = await handler(req, context);
12
- if (response && typeof response.status === 'number') {
13
- status = response.status;
13
+ // 2. Run in Context
14
+ return client.startTrace({
15
+ method,
16
+ path: url.pathname,
17
+ userAgent: ua,
18
+ ip: ip
19
+ }, async () => {
20
+ try {
21
+ const response = await handler(req, context);
22
+ const status = response?.status || 200;
23
+
24
+ client.endTrace(status, { route: normalizePath(url.pathname) });
25
+ return response;
26
+ } catch (err: any) {
27
+ client.endTrace(500, { route: normalizePath(url.pathname) });
28
+ throw err;
14
29
  }
15
- return response;
16
- } catch (err: any) {
17
- status = 500;
18
- throw err;
19
- } finally {
20
- const duration = performance.now() - start;
21
-
22
- // App Router Request is a standard Web Request object
23
- const url = req.url ? new URL(req.url) : { pathname: '/' };
24
-
25
- // In App Router, we often rely on file-system path, but context.params helps
26
- // For now, robust heuristic normalization is best
27
- const route = normalizePath(url.pathname);
28
-
29
- client.track({
30
- method: req.method || 'GET',
31
- route: route,
32
- path: url.pathname,
33
- status: status,
34
- duration: duration,
35
- userAgent: req.headers.get ? req.headers.get('user-agent') : undefined,
36
- // IP extraction from Web Request is tricky without headers
37
- ip: req.headers.get ? req.headers.get('x-forwarded-for') : undefined,
38
- });
39
-
40
- // Vercel/Serverless Flush Safety
41
- // We purposefully do NOT await flush here to avoid latency.
42
- // Ideally user configures transport to sync flush or uses waitUntil
43
- }
30
+ });
44
31
  };
45
32
  };
46
33
 
47
- // --- Pages Router Wrapper (req, res) ---
34
+ // --- Pages Router Wrapper (API Routes) ---
48
35
  export const wrapNextPages = (handler: Function) => {
49
36
  return async (req: any, res: any) => {
50
- const start = performance.now();
37
+ const path = req.url ? req.url.split('?')[0] : '/';
38
+
39
+ // 1. Run in Context
40
+ return client.startTrace({
41
+ method: req.method || 'GET',
42
+ path: path,
43
+ userAgent: req.headers['user-agent'],
44
+ ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,
45
+ }, async () => {
46
+
47
+ // 2. Hook Response
48
+ const done = () => {
49
+ client.endTrace(res.statusCode || 200, { route: normalizePath(path) });
50
+ };
51
+
52
+ res.once('finish', done);
53
+ res.once('close', done); // Fallback if finish doesn't fire
51
54
 
52
- // Hook into response finish
53
- const done = () => {
54
- const duration = performance.now() - start;
55
- const path = req.url || '/';
56
-
57
- client.track({
58
- method: req.method || 'GET',
59
- route: normalizePath(path.split('?')[0]),
60
- path: path,
61
- status: res.statusCode || 200,
62
- duration: duration,
63
- ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,
64
- userAgent: req.headers['user-agent']
65
- });
66
- };
67
-
68
- res.once('finish', done);
69
- // Handle error case if next/error isn't used
70
- res.once('close', done);
71
-
72
- return handler(req, res);
55
+ // 3. Execute
56
+ try {
57
+ 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
61
+ throw e;
62
+ }
63
+ });
73
64
  };
74
65
  };
package/wiki.md CHANGED
@@ -94,4 +94,27 @@ import { Senzor } from '@senzops/apm-node';
94
94
 
95
95
  fastify.register(Senzor.fastifyPlugin, { apiKey: '...' });
96
96
  ```
97
- This approach provides **native robustness** for each framework. It captures errors (500s), 404s (Route not found), and correct timing without the user manually writing `track()` calls.
97
+ This approach provides **native robustness** for each framework. It captures errors (500s), 404s (Route not found), and correct timing without the user manually writing `track()` calls.
98
+
99
+ ## How Users Add Spans (Waterfall)
100
+ Now your users can do this in their Express/Next.js apps:
101
+
102
+ ```js
103
+ app.get('/users', async (req, res) => {
104
+ // 1. Start a span
105
+ const dbSpan = Senzor.startSpan('fetch_users_db', 'db');
106
+
107
+ const users = await db.query('SELECT * FROM users');
108
+
109
+ // 2. End span (Duration calculated automatically)
110
+ dbSpan.end({ query: 'SELECT * FROM users' });
111
+
112
+ // 3. Start another span
113
+ const apiSpan = Senzor.startSpan('stripe_api', 'http');
114
+ await axios.get('...');
115
+ apiSpan.end({}, 200);
116
+
117
+ res.json(users);
118
+ });
119
+ ```
120
+ This will automatically appear in your **Waterfall Chart** on the dashboard, nested under the main request Trace.