@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.
- package/dist/index.d.mts +0 -9
- package/dist/index.d.ts +0 -9
- 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 +80 -27
- package/src/core/context.ts +26 -0
- package/src/core/transport.ts +12 -23
- package/src/core/types.ts +37 -0
- package/src/index.ts +2 -2
- package/src/instrumentation/http.ts +114 -0
- package/src/instrumentation/mongo.ts +74 -0
- package/src/instrumentation/pg.ts +41 -0
- package/src/middleware/express.ts +22 -32
- package/src/wrappers/fastify.ts +1 -1
- package/src/wrappers/h3.ts +26 -38
- package/src/wrappers/next.ts +50 -59
- package/wiki.md +24 -1
package/src/wrappers/next.ts
CHANGED
|
@@ -1,74 +1,65 @@
|
|
|
1
1
|
import { client } from '../core/client';
|
|
2
2
|
import { normalizePath } from '../core/normalizer';
|
|
3
3
|
|
|
4
|
-
// --- App Router Wrapper (
|
|
4
|
+
// --- App Router Wrapper (Route Handlers) ---
|
|
5
5
|
export const wrapNextRoute = (handler: Function) => {
|
|
6
6
|
return async (req: Request | any, context?: any) => {
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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 (
|
|
34
|
+
// --- Pages Router Wrapper (API Routes) ---
|
|
48
35
|
export const wrapNextPages = (handler: Function) => {
|
|
49
36
|
return async (req: any, res: any) => {
|
|
50
|
-
const
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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.
|