@senzops/apm-node 1.2.0 → 1.2.2
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/.claude/worktrees/infallible-chatelet-f3fb36/.claude/settings.local.json +9 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/CHANGELOG.md +49 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/README.md +398 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/package-lock.json +1494 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/package.json +42 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/client.ts +451 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/context.ts +48 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/normalizer.ts +44 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/sanitizer.ts +203 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/transport.ts +273 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/types.ts +106 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/index.ts +36 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/bullmq.ts +195 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/cron.ts +204 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/express.ts +338 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/fastify.ts +296 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/framework.ts +301 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/hook.ts +134 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/http.ts +530 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/koa.ts +173 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongo.ts +202 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongoose.ts +156 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mysql.ts +169 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/patch.ts +56 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/pg.ts +131 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/redis.ts +109 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/span.ts +73 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/undici.ts +189 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/middleware/express.ts +48 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/register.ts +58 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/getClientIp.ts +175 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/ids.ts +7 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/internal.ts +1 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/sdkMeta.ts +6 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/traceContext.ts +44 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/fastify.ts +35 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/h3.ts +59 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/next.ts +131 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/tsconfig.json +15 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/tsup.config.ts +21 -0
- package/.claude/worktrees/infallible-chatelet-f3fb36/wiki.md +852 -0
- package/CHANGELOG.md +8 -0
- package/README.md +12 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -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/dist/register.js +1 -1
- package/dist/register.js.map +1 -1
- package/dist/register.mjs +1 -1
- package/dist/register.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/client.ts +8 -2
- package/src/core/types.ts +5 -0
- package/src/instrumentation/express.ts +338 -0
- package/src/instrumentation/fastify.ts +296 -0
- package/src/instrumentation/framework.ts +301 -0
- package/src/instrumentation/hook.ts +79 -192
- package/src/instrumentation/koa.ts +173 -0
- package/src/register.ts +16 -0
- package/src/wrappers/fastify.ts +10 -7
- package/src/wrappers/h3.ts +40 -16
- package/src/wrappers/next.ts +68 -21
- package/wiki.md +8 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getClientIp.ts
|
|
3
|
+
*
|
|
4
|
+
* Robust, proxy-aware client IP extraction.
|
|
5
|
+
*
|
|
6
|
+
* Priority order (mirrors Umami + industry best practice):
|
|
7
|
+
* 1. ENV-configured custom header (CLIENT_IP_HEADER)
|
|
8
|
+
* 2. CF-Connecting-IP (Cloudflare — single trusted IP)
|
|
9
|
+
* 3. True-Client-IP (Cloudflare Enterprise / Akamai)
|
|
10
|
+
* 4. X-Real-IP (Nginx realip module)
|
|
11
|
+
* 5. Forwarded (RFC 7239 — "for=" field)
|
|
12
|
+
* 6. X-Forwarded-For (De-facto standard — leftmost public IP)
|
|
13
|
+
* 7. req.socket.remoteAddress (Direct connection fallback)
|
|
14
|
+
*
|
|
15
|
+
* Security note: headers 2-6 can be spoofed by clients when your server is
|
|
16
|
+
* directly internet-facing. If that is a concern, restrict extraction to the
|
|
17
|
+
* header your trusted reverse-proxy injects (CLIENT_IP_HEADER or X-Real-IP).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { isIP } from "net";
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/** Strip IPv4-mapped IPv6 prefix (::ffff:1.2.3.4 → 1.2.3.4) */
|
|
27
|
+
const stripIPv6Mapped = (ip: string): string =>
|
|
28
|
+
ip.startsWith("::ffff:") ? ip.slice(7) : ip;
|
|
29
|
+
|
|
30
|
+
/** Strip optional port from an IPv4 address (1.2.3.4:5678 → 1.2.3.4). */
|
|
31
|
+
const stripIPv4Port = (ip: string): string => {
|
|
32
|
+
const lastColon = ip.lastIndexOf(":");
|
|
33
|
+
if (lastColon === -1) return ip;
|
|
34
|
+
const maybeIP = ip.slice(0, lastColon);
|
|
35
|
+
return isIP(maybeIP) === 4 ? maybeIP : ip;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Strip brackets + optional port from an IPv6 address ([::1]:5678 → ::1). */
|
|
39
|
+
const stripIPv6Brackets = (ip: string): string => {
|
|
40
|
+
const match = ip.match(/^\[([^\]]+)\](?::\d+)?$/);
|
|
41
|
+
return match ? match[1] : ip;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Normalise raw IP string into a clean, routable address (or null). */
|
|
45
|
+
export const normaliseIP = (raw: string | undefined | null): string | null => {
|
|
46
|
+
if (!raw) return null;
|
|
47
|
+
let ip = raw.trim();
|
|
48
|
+
if (!ip) return null;
|
|
49
|
+
|
|
50
|
+
ip = stripIPv6Brackets(ip);
|
|
51
|
+
ip = stripIPv4Port(ip);
|
|
52
|
+
ip = stripIPv6Mapped(ip);
|
|
53
|
+
|
|
54
|
+
return isIP(ip) !== 0 ? ip : null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns true for IPs that will never produce a geo result:
|
|
59
|
+
* loopback, link-local, private ranges, and unspecified addresses.
|
|
60
|
+
*/
|
|
61
|
+
export const isPrivateOrLoopback = (ip: string): boolean => {
|
|
62
|
+
// IPv4 private / loopback / link-local
|
|
63
|
+
if (
|
|
64
|
+
ip === "127.0.0.1" ||
|
|
65
|
+
ip.startsWith("10.") ||
|
|
66
|
+
ip.startsWith("192.168.") ||
|
|
67
|
+
ip.startsWith("169.254.") || // link-local
|
|
68
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(ip) // 172.16–31
|
|
69
|
+
)
|
|
70
|
+
return true;
|
|
71
|
+
|
|
72
|
+
// IPv6 loopback / unspecified / link-local / unique-local
|
|
73
|
+
if (
|
|
74
|
+
ip === "::1" ||
|
|
75
|
+
ip === "::" ||
|
|
76
|
+
ip.toLowerCase().startsWith("fe80:") || // link-local
|
|
77
|
+
ip.toLowerCase().startsWith("fc") || // unique-local
|
|
78
|
+
ip.toLowerCase().startsWith("fd") // unique-local
|
|
79
|
+
)
|
|
80
|
+
return true;
|
|
81
|
+
|
|
82
|
+
return false;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// RFC 7239 "Forwarded" header parser
|
|
87
|
+
// e.g. Forwarded: for=192.0.2.60;proto=http, for="[2001:db8::cafe]"
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
const parseForwardedHeader = (header: string): string | null => {
|
|
90
|
+
const parts = header.split(",");
|
|
91
|
+
for (const part of parts) {
|
|
92
|
+
const forMatch = part.match(/for=["[]?([^\]",;>\s]+)/i);
|
|
93
|
+
if (forMatch) {
|
|
94
|
+
const ip = normaliseIP(forMatch[1]);
|
|
95
|
+
if (ip && !isPrivateOrLoopback(ip)) return ip;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// X-Forwarded-For parser — pick the leftmost *public* IP
|
|
103
|
+
// e.g. X-Forwarded-For: client, proxy1, proxy2
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
const parseXForwardedFor = (header: string): string | null => {
|
|
106
|
+
const ips = header.split(",").map((s) => s.trim());
|
|
107
|
+
for (const raw of ips) {
|
|
108
|
+
const ip = normaliseIP(raw);
|
|
109
|
+
if (ip && !isPrivateOrLoopback(ip)) return ip;
|
|
110
|
+
}
|
|
111
|
+
// If every hop is private (intranet-only setup) fall back to first valid IP
|
|
112
|
+
for (const raw of ips) {
|
|
113
|
+
const ip = normaliseIP(raw);
|
|
114
|
+
if (ip) return ip;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Main export
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Extract the best-available client IP from a request.
|
|
125
|
+
*
|
|
126
|
+
* Returns `null` if no valid IP can be determined.
|
|
127
|
+
*/
|
|
128
|
+
export const getClientIp = (req: any): string | null => {
|
|
129
|
+
const h = req.headers;
|
|
130
|
+
|
|
131
|
+
// 2. Cloudflare single-IP header (most reliable when behind CF)
|
|
132
|
+
{
|
|
133
|
+
const ip = normaliseIP(h["cf-connecting-ip"] as string);
|
|
134
|
+
if (ip) return ip;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 3. Cloudflare Enterprise / Akamai
|
|
138
|
+
{
|
|
139
|
+
const ip = normaliseIP(h["true-client-ip"] as string);
|
|
140
|
+
if (ip) return ip;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 4. Nginx realip module (single, already-trusted IP)
|
|
144
|
+
{
|
|
145
|
+
const ip = normaliseIP(h["x-real-ip"] as string);
|
|
146
|
+
if (ip) return ip;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 5. RFC 7239 Forwarded header
|
|
150
|
+
{
|
|
151
|
+
const fwd = h["forwarded"] as string;
|
|
152
|
+
if (fwd) {
|
|
153
|
+
const ip = parseForwardedHeader(fwd);
|
|
154
|
+
if (ip) return ip;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 6. De-facto standard XFF
|
|
159
|
+
{
|
|
160
|
+
const xff = h["x-forwarded-for"] as string;
|
|
161
|
+
if (xff) {
|
|
162
|
+
const ip = parseXForwardedFor(xff);
|
|
163
|
+
if (ip) return ip;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 7. Direct TCP connection (local dev / no proxy)
|
|
168
|
+
{
|
|
169
|
+
const raw = req.socket?.remoteAddress;
|
|
170
|
+
const ip = normaliseIP(raw);
|
|
171
|
+
if (ip) return ip;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SENZOR_INTERNAL_HEADER = 'x-senzor-sdk-internal';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3C Trace Context Implementation
|
|
3
|
+
* Standard: https://www.w3.org/TR/trace-context/
|
|
4
|
+
* Format: 00-{traceId}-{spanId}-{traceFlags}
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface TraceContext {
|
|
8
|
+
traceId: string;
|
|
9
|
+
parentSpanId: string;
|
|
10
|
+
sampled: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TRACEPARENT_REGEX = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
14
|
+
|
|
15
|
+
export const parseTraceparent = (header?: string | string[]): TraceContext | null => {
|
|
16
|
+
if (!header) return null;
|
|
17
|
+
|
|
18
|
+
const traceparent = Array.isArray(header) ? header[0] : header;
|
|
19
|
+
if (typeof traceparent !== 'string') return null;
|
|
20
|
+
|
|
21
|
+
const match = traceparent.trim().toLowerCase().match(TRACEPARENT_REGEX);
|
|
22
|
+
if (!match) return null;
|
|
23
|
+
|
|
24
|
+
const traceId = match[1];
|
|
25
|
+
const parentSpanId = match[2];
|
|
26
|
+
const flags = match[3];
|
|
27
|
+
|
|
28
|
+
// Invalid IDs according to W3C specification
|
|
29
|
+
if (traceId === '00000000000000000000000000000000') return null;
|
|
30
|
+
if (parentSpanId === '0000000000000000') return null;
|
|
31
|
+
|
|
32
|
+
// The least significant bit of flags indicates if the trace is sampled
|
|
33
|
+
const sampled = (parseInt(flags, 16) & 0x01) === 0x01;
|
|
34
|
+
|
|
35
|
+
return { traceId, parentSpanId, sampled };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generates a valid W3C traceparent header string for OUTGOING requests.
|
|
40
|
+
*/
|
|
41
|
+
export const generateTraceparent = (traceId: string, spanId: string, sampled: boolean = true): string => {
|
|
42
|
+
const flags = sampled ? '01' : '00';
|
|
43
|
+
return `00-${traceId}-${spanId}-${flags}`;
|
|
44
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { client } from '../core/client';
|
|
2
|
+
import { SenzorOptions } from '../core/types';
|
|
3
|
+
import { instrumentFastifyInstance } from '../instrumentation/fastify';
|
|
4
|
+
import { getClientIp } from '../utils/getClientIp';
|
|
5
|
+
|
|
6
|
+
export const senzorPlugin = (fastify: any, options: SenzorOptions, done: Function) => {
|
|
7
|
+
if (options && options.apiKey) {
|
|
8
|
+
client.init(options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
instrumentFastifyInstance(fastify, options);
|
|
12
|
+
|
|
13
|
+
fastify.addHook('onRequest', (request: any, reply: any, next: Function) => {
|
|
14
|
+
client.startTrace({
|
|
15
|
+
method: request.method,
|
|
16
|
+
path: request.raw.url || request.url,
|
|
17
|
+
ip: getClientIp(request),
|
|
18
|
+
userAgent: request.headers['user-agent'],
|
|
19
|
+
headers: request.headers // Pass headers
|
|
20
|
+
}, () => next());
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
fastify.addHook('onError', (request: any, reply: any, error: any, next: Function) => {
|
|
24
|
+
client.captureError(error);
|
|
25
|
+
next();
|
|
26
|
+
});
|
|
27
|
+
|
|
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 });
|
|
31
|
+
next();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
done();
|
|
35
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { client } from '../core/client';
|
|
2
|
+
import { getRoute } from '../core/normalizer';
|
|
3
|
+
import { invokeWithFrameworkSpan } from '../instrumentation/framework';
|
|
4
|
+
import { getClientIp } from '../utils/getClientIp';
|
|
5
|
+
|
|
6
|
+
type EventHandler = (event: any) => any;
|
|
7
|
+
|
|
8
|
+
export const wrapH3 = (handler: EventHandler) => {
|
|
9
|
+
return (event: any) => {
|
|
10
|
+
const req = event.node.req;
|
|
11
|
+
const path = req.originalUrl || req.url || '/';
|
|
12
|
+
|
|
13
|
+
return client.startTrace({
|
|
14
|
+
method: req.method || 'GET',
|
|
15
|
+
path: path,
|
|
16
|
+
ip: getClientIp(req),
|
|
17
|
+
userAgent: req.headers['user-agent'],
|
|
18
|
+
headers: req.headers // Pass headers
|
|
19
|
+
}, async () => {
|
|
20
|
+
try {
|
|
21
|
+
const route = getRoute(event, path);
|
|
22
|
+
const response = await invokeWithFrameworkSpan(
|
|
23
|
+
handler,
|
|
24
|
+
undefined,
|
|
25
|
+
[event],
|
|
26
|
+
{
|
|
27
|
+
framework: 'h3',
|
|
28
|
+
type: 'event_handler',
|
|
29
|
+
name: `h3.event_handler ${req.method || 'GET'} ${route}`,
|
|
30
|
+
route,
|
|
31
|
+
method: req.method || 'GET',
|
|
32
|
+
request: req,
|
|
33
|
+
response: event.node.res,
|
|
34
|
+
attributes: {
|
|
35
|
+
'h3.type': 'event_handler',
|
|
36
|
+
'http.route': route
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
undefined,
|
|
40
|
+
{
|
|
41
|
+
callbackCompletesSpan: false,
|
|
42
|
+
responseEndsSpan: false
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
let status = 200;
|
|
46
|
+
if (event.node.res.statusCode) status = event.node.res.statusCode;
|
|
47
|
+
if (response && response.statusCode) status = response.statusCode;
|
|
48
|
+
|
|
49
|
+
client.endTrace(status, { route });
|
|
50
|
+
return response;
|
|
51
|
+
} catch (err: any) {
|
|
52
|
+
client.captureError(err);
|
|
53
|
+
const status = err.statusCode || err.status || 500;
|
|
54
|
+
client.endTrace(status, { route: getRoute(event, path) });
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { client } from '../core/client';
|
|
2
|
+
import { normalizePath } from '../core/normalizer';
|
|
3
|
+
import { invokeWithFrameworkSpan } from '../instrumentation/framework';
|
|
4
|
+
import { getClientIp } from '../utils/getClientIp';
|
|
5
|
+
|
|
6
|
+
// --- App Router Wrapper ---
|
|
7
|
+
export const wrapNextRoute = (handler: Function) => {
|
|
8
|
+
return async (req: Request | any, context?: any) => {
|
|
9
|
+
|
|
10
|
+
// Extract info from Web Standard Request
|
|
11
|
+
const url = req.url ? new URL(req.url) : { pathname: '/' };
|
|
12
|
+
const method = req.method || 'GET';
|
|
13
|
+
|
|
14
|
+
// Header Extraction
|
|
15
|
+
let headers: Record<string, string> = {};
|
|
16
|
+
let ua: string | undefined;
|
|
17
|
+
let ip: string | undefined;
|
|
18
|
+
|
|
19
|
+
if (typeof req.headers.get === 'function') {
|
|
20
|
+
// It's a Web Request Object
|
|
21
|
+
ua = req.headers.get('user-agent');
|
|
22
|
+
ip = req.headers.get('x-forwarded-for');
|
|
23
|
+
|
|
24
|
+
// Convert to plain object for trace context extraction
|
|
25
|
+
req.headers.forEach((value: string, key: string) => {
|
|
26
|
+
headers[key] = value;
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
// It's a Node Request Object (rare in App router but possible)
|
|
30
|
+
headers = req.headers;
|
|
31
|
+
ua = headers['user-agent'];
|
|
32
|
+
ip = headers['x-forwarded-for'] as string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return client.startTrace({
|
|
36
|
+
method,
|
|
37
|
+
path: url.pathname,
|
|
38
|
+
userAgent: ua,
|
|
39
|
+
ip: ip || getClientIp(req),
|
|
40
|
+
headers: headers // Pass extracted headers
|
|
41
|
+
}, async () => {
|
|
42
|
+
try {
|
|
43
|
+
const route = normalizePath(url.pathname);
|
|
44
|
+
const response = await invokeWithFrameworkSpan(
|
|
45
|
+
handler,
|
|
46
|
+
undefined,
|
|
47
|
+
[req, context],
|
|
48
|
+
{
|
|
49
|
+
framework: 'next',
|
|
50
|
+
type: 'route_handler',
|
|
51
|
+
name: `next.app_route_handler ${method} ${route}`,
|
|
52
|
+
route,
|
|
53
|
+
method,
|
|
54
|
+
request: req,
|
|
55
|
+
attributes: {
|
|
56
|
+
'next.router': 'app',
|
|
57
|
+
'http.route': route,
|
|
58
|
+
'url.path': url.pathname
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
undefined,
|
|
62
|
+
{
|
|
63
|
+
callbackCompletesSpan: false,
|
|
64
|
+
responseEndsSpan: false
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
const status = response?.status || 200;
|
|
68
|
+
|
|
69
|
+
client.endTrace(status, { route });
|
|
70
|
+
return response;
|
|
71
|
+
} catch (err: any) {
|
|
72
|
+
client.captureError(err);
|
|
73
|
+
client.endTrace(500, { route: normalizePath(url.pathname) });
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// --- Pages Router Wrapper ---
|
|
81
|
+
export const wrapNextPages = (handler: Function) => {
|
|
82
|
+
return async (req: any, res: any) => {
|
|
83
|
+
const path = req.url ? req.url.split('?')[0] : '/';
|
|
84
|
+
|
|
85
|
+
return client.startTrace({
|
|
86
|
+
method: req.method || 'GET',
|
|
87
|
+
path: path,
|
|
88
|
+
userAgent: req.headers['user-agent'],
|
|
89
|
+
ip: getClientIp(req),
|
|
90
|
+
headers: req.headers // Standard Node headers work fine
|
|
91
|
+
}, async () => {
|
|
92
|
+
|
|
93
|
+
const done = () => {
|
|
94
|
+
client.endTrace(res.statusCode || 200, { route: normalizePath(path) });
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
res.once('finish', done);
|
|
98
|
+
res.once('close', done);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const route = normalizePath(path);
|
|
102
|
+
return await invokeWithFrameworkSpan(
|
|
103
|
+
handler,
|
|
104
|
+
undefined,
|
|
105
|
+
[req, res],
|
|
106
|
+
{
|
|
107
|
+
framework: 'next',
|
|
108
|
+
type: 'route_handler',
|
|
109
|
+
name: `next.pages_api_handler ${req.method || 'GET'} ${route}`,
|
|
110
|
+
route,
|
|
111
|
+
method: req.method || 'GET',
|
|
112
|
+
request: req,
|
|
113
|
+
response: res,
|
|
114
|
+
attributes: {
|
|
115
|
+
'next.router': 'pages',
|
|
116
|
+
'http.route': route
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
undefined,
|
|
120
|
+
{
|
|
121
|
+
callbackCompletesSpan: false,
|
|
122
|
+
responseEndsSpan: true
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
} catch (e: any) {
|
|
126
|
+
client.captureError(e);
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig([
|
|
4
|
+
{
|
|
5
|
+
entry: ['src/index.ts', 'src/register.ts'],
|
|
6
|
+
format: ['cjs', 'esm'],
|
|
7
|
+
dts: true,
|
|
8
|
+
clean: true,
|
|
9
|
+
minify: true,
|
|
10
|
+
sourcemap: true,
|
|
11
|
+
splitting: false,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
entry: ['src/index.ts'],
|
|
15
|
+
format: ['iife'],
|
|
16
|
+
clean: false,
|
|
17
|
+
minify: true,
|
|
18
|
+
sourcemap: true,
|
|
19
|
+
splitting: false,
|
|
20
|
+
}
|
|
21
|
+
]);
|