@justanalyticsapp/node 0.1.0
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/client.d.ts +286 -0
- package/dist/client.js +681 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +126 -0
- package/dist/context.js +170 -0
- package/dist/context.js.map +1 -0
- package/dist/errors.d.ts +135 -0
- package/dist/errors.js +180 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +301 -0
- package/dist/index.js +314 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/express.d.ts +77 -0
- package/dist/integrations/express.js +87 -0
- package/dist/integrations/express.js.map +1 -0
- package/dist/integrations/http.d.ts +129 -0
- package/dist/integrations/http.js +465 -0
- package/dist/integrations/http.js.map +1 -0
- package/dist/integrations/metrics.d.ts +110 -0
- package/dist/integrations/metrics.js +313 -0
- package/dist/integrations/metrics.js.map +1 -0
- package/dist/integrations/next.d.ts +252 -0
- package/dist/integrations/next.js +480 -0
- package/dist/integrations/next.js.map +1 -0
- package/dist/integrations/pg.d.ts +169 -0
- package/dist/integrations/pg.js +616 -0
- package/dist/integrations/pg.js.map +1 -0
- package/dist/integrations/pino.d.ts +52 -0
- package/dist/integrations/pino.js +153 -0
- package/dist/integrations/pino.js.map +1 -0
- package/dist/integrations/redis.d.ts +190 -0
- package/dist/integrations/redis.js +597 -0
- package/dist/integrations/redis.js.map +1 -0
- package/dist/integrations/winston.d.ts +48 -0
- package/dist/integrations/winston.js +99 -0
- package/dist/integrations/winston.js.map +1 -0
- package/dist/logger.d.ts +148 -0
- package/dist/logger.js +162 -0
- package/dist/logger.js.map +1 -0
- package/dist/span.d.ts +192 -0
- package/dist/span.js +197 -0
- package/dist/span.js.map +1 -0
- package/dist/transport.d.ts +246 -0
- package/dist/transport.js +654 -0
- package/dist/transport.js.map +1 -0
- package/dist/utils/headers.d.ts +60 -0
- package/dist/utils/headers.js +93 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/id.d.ts +23 -0
- package/dist/utils/id.js +36 -0
- package/dist/utils/id.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file packages/node-sdk/src/integrations/next.ts
|
|
4
|
+
* @description Next.js integration for JustAnalytics Node.js SDK.
|
|
5
|
+
*
|
|
6
|
+
* Implements Story 044 - Next.js Integration
|
|
7
|
+
*
|
|
8
|
+
* Provides zero-config initialization via Next.js 15+ instrumentation.ts hook,
|
|
9
|
+
* API route tracing wrappers, middleware tracing, and Server Component helpers.
|
|
10
|
+
*
|
|
11
|
+
* Quick Start (Next.js 15+):
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // instrumentation.ts
|
|
14
|
+
* export { register } from '@justanalyticsapp/node/next';
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* With custom config:
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // instrumentation.ts
|
|
20
|
+
* import { withJustAnalytics } from '@justanalyticsapp/node/next';
|
|
21
|
+
*
|
|
22
|
+
* export function register() {
|
|
23
|
+
* withJustAnalytics({
|
|
24
|
+
* siteId: 'site_abc123',
|
|
25
|
+
* apiKey: 'ja_sk_...',
|
|
26
|
+
* serviceName: 'my-next-app',
|
|
27
|
+
* environment: 'production',
|
|
28
|
+
* });
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* API Route Tracing:
|
|
33
|
+
* ```typescript
|
|
34
|
+
* // app/api/users/[id]/route.ts
|
|
35
|
+
* import { withTracing } from '@justanalyticsapp/node/next';
|
|
36
|
+
*
|
|
37
|
+
* export const GET = withTracing(async (req, { params }) => {
|
|
38
|
+
* const user = await db.users.findUnique({ where: { id: params.id } });
|
|
39
|
+
* return Response.json(user);
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Middleware Tracing:
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // middleware.ts
|
|
46
|
+
* import { withMiddlewareTracing } from '@justanalyticsapp/node/next';
|
|
47
|
+
*
|
|
48
|
+
* export default withMiddlewareTracing(async (req) => {
|
|
49
|
+
* // your middleware logic
|
|
50
|
+
* return NextResponse.next();
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Server Component Tracing:
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // app/users/[id]/page.tsx
|
|
57
|
+
* import { traceServerComponent } from '@justanalyticsapp/node/next';
|
|
58
|
+
*
|
|
59
|
+
* export default async function UserPage({ params }) {
|
|
60
|
+
* return traceServerComponent('UserPage', async () => {
|
|
61
|
+
* const user = await getUser(params.id);
|
|
62
|
+
* return <UserProfile user={user} />;
|
|
63
|
+
* });
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* References:
|
|
68
|
+
* - Story 035 - Node.js SDK Core (JA.init, JA.startSpan, context)
|
|
69
|
+
* - Story 036 - HTTP Auto-Instrumentation (parent span creation)
|
|
70
|
+
* - Next.js instrumentation.ts: https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
|
|
71
|
+
*/
|
|
72
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
73
|
+
exports.withJustAnalytics = withJustAnalytics;
|
|
74
|
+
exports.register = register;
|
|
75
|
+
exports.extractRoutePattern = extractRoutePattern;
|
|
76
|
+
exports.withTracing = withTracing;
|
|
77
|
+
exports.withMiddlewareTracing = withMiddlewareTracing;
|
|
78
|
+
exports.traceServerComponent = traceServerComponent;
|
|
79
|
+
// Import SDK functions lazily via require to avoid circular dependencies
|
|
80
|
+
// and to ensure we use the singleton from index.ts. We import the client
|
|
81
|
+
// class directly since the singleton is created in index.ts.
|
|
82
|
+
const client_1 = require("../client");
|
|
83
|
+
/**
|
|
84
|
+
* Internal reference to the singleton SDK client.
|
|
85
|
+
*
|
|
86
|
+
* We create our own singleton reference here rather than importing from
|
|
87
|
+
* index.ts to avoid circular dependency issues when this file is imported
|
|
88
|
+
* via the `./next` export path. The `withJustAnalytics()` function creates
|
|
89
|
+
* and initializes this client.
|
|
90
|
+
*/
|
|
91
|
+
let _client = null;
|
|
92
|
+
/**
|
|
93
|
+
* Get the SDK client, creating it if necessary.
|
|
94
|
+
*
|
|
95
|
+
* @returns The JustAnalyticsClient instance, or null if not yet initialized
|
|
96
|
+
*/
|
|
97
|
+
function getClient() {
|
|
98
|
+
return _client;
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// withJustAnalytics()
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
/**
|
|
104
|
+
* Initialize JustAnalytics with Next.js-optimized defaults.
|
|
105
|
+
*
|
|
106
|
+
* Reads configuration from environment variables when not explicitly provided.
|
|
107
|
+
* Safe to call multiple times (idempotent -- delegates to JA.init()'s guard).
|
|
108
|
+
*
|
|
109
|
+
* Environment variables (in order of priority: explicit config > env var > default):
|
|
110
|
+
* - `JUSTANALYTICS_SITE_ID` -- site ID
|
|
111
|
+
* - `JUSTANALYTICS_API_KEY` -- API key
|
|
112
|
+
* - `JUSTANALYTICS_SERVICE_NAME` -- service name (default: `'next-app'`)
|
|
113
|
+
* - `JUSTANALYTICS_ENVIRONMENT` -- environment (default: `NODE_ENV || 'development'`)
|
|
114
|
+
* - `JUSTANALYTICS_URL` -- server URL
|
|
115
|
+
*
|
|
116
|
+
* If `siteId` or `apiKey` are missing after merging config and env vars,
|
|
117
|
+
* the function logs a warning and returns without calling `init()`.
|
|
118
|
+
*
|
|
119
|
+
* @param config - Optional configuration overrides
|
|
120
|
+
*/
|
|
121
|
+
function withJustAnalytics(config) {
|
|
122
|
+
try {
|
|
123
|
+
// Skip initialization on edge runtime (SDK uses Node.js APIs)
|
|
124
|
+
if (typeof process !== 'undefined' && process.env.NEXT_RUNTIME === 'edge') {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Read environment variables
|
|
128
|
+
const envSiteId = typeof process !== 'undefined' ? process.env.JUSTANALYTICS_SITE_ID : undefined;
|
|
129
|
+
const envApiKey = typeof process !== 'undefined' ? process.env.JUSTANALYTICS_API_KEY : undefined;
|
|
130
|
+
const envServiceName = typeof process !== 'undefined' ? process.env.JUSTANALYTICS_SERVICE_NAME : undefined;
|
|
131
|
+
const envEnvironment = typeof process !== 'undefined' ? process.env.JUSTANALYTICS_ENVIRONMENT : undefined;
|
|
132
|
+
const envUrl = typeof process !== 'undefined' ? process.env.JUSTANALYTICS_URL : undefined;
|
|
133
|
+
const envNodeEnv = typeof process !== 'undefined' ? process.env.NODE_ENV : undefined;
|
|
134
|
+
// Merge: explicit config > env vars > defaults
|
|
135
|
+
const siteId = config?.siteId || envSiteId || '';
|
|
136
|
+
const apiKey = config?.apiKey || envApiKey || '';
|
|
137
|
+
const serviceName = config?.serviceName || envServiceName || 'next-app';
|
|
138
|
+
const environment = config?.environment || envEnvironment || envNodeEnv || 'development';
|
|
139
|
+
const serverUrl = config?.serverUrl || envUrl;
|
|
140
|
+
// Validate required fields
|
|
141
|
+
if (!siteId || !apiKey) {
|
|
142
|
+
console.warn('[JustAnalytics] Missing siteId or apiKey. SDK will not initialize.');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Create client if not already created
|
|
146
|
+
if (!_client) {
|
|
147
|
+
_client = new client_1.JustAnalyticsClient();
|
|
148
|
+
}
|
|
149
|
+
// Build init options, stripping Next.js-specific fields
|
|
150
|
+
const initOptions = {
|
|
151
|
+
siteId,
|
|
152
|
+
apiKey,
|
|
153
|
+
serviceName,
|
|
154
|
+
environment,
|
|
155
|
+
...(serverUrl ? { serverUrl } : {}),
|
|
156
|
+
...(config?.release ? { release: config.release } : {}),
|
|
157
|
+
...(config?.debug !== undefined ? { debug: config.debug } : {}),
|
|
158
|
+
...(config?.flushIntervalMs !== undefined ? { flushIntervalMs: config.flushIntervalMs } : {}),
|
|
159
|
+
...(config?.maxBatchSize !== undefined ? { maxBatchSize: config.maxBatchSize } : {}),
|
|
160
|
+
...(config?.enabled !== undefined ? { enabled: config.enabled } : {}),
|
|
161
|
+
...(config?.integrations ? { integrations: config.integrations } : {}),
|
|
162
|
+
...(config?.enableUncaughtExceptionHandler !== undefined
|
|
163
|
+
? { enableUncaughtExceptionHandler: config.enableUncaughtExceptionHandler }
|
|
164
|
+
: {}),
|
|
165
|
+
...(config?.enableUnhandledRejectionHandler !== undefined
|
|
166
|
+
? { enableUnhandledRejectionHandler: config.enableUnhandledRejectionHandler }
|
|
167
|
+
: {}),
|
|
168
|
+
};
|
|
169
|
+
_client.init(initOptions);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Never throw from initialization -- the user's app must not be affected
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// register()
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
/**
|
|
179
|
+
* Next.js instrumentation.ts register() hook.
|
|
180
|
+
*
|
|
181
|
+
* Export this directly from your instrumentation.ts:
|
|
182
|
+
* ```typescript
|
|
183
|
+
* export { register } from '@justanalyticsapp/node/next';
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* Only initializes on the Node.js runtime (skips edge runtime).
|
|
187
|
+
* Designed for Next.js 15+ where `instrumentation.ts` is stable.
|
|
188
|
+
* For Next.js 13-14, call `withJustAnalytics()` manually.
|
|
189
|
+
*/
|
|
190
|
+
function register() {
|
|
191
|
+
try {
|
|
192
|
+
// Skip on edge runtime -- SDK uses Node.js-only APIs
|
|
193
|
+
if (typeof process !== 'undefined' && process.env.NEXT_RUNTIME === 'edge') {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
withJustAnalytics();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Never throw from the register hook
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// extractRoutePattern()
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
/**
|
|
206
|
+
* Extract the Next.js route pattern from a request.
|
|
207
|
+
*
|
|
208
|
+
* Checks Next.js internal headers in priority order:
|
|
209
|
+
* 1. `x-invoke-path` -- Next.js 14.1+ sets this to the file-system route
|
|
210
|
+
* 2. `x-matched-path` -- Some Next.js versions use this header
|
|
211
|
+
* 3. `x-nextjs-page` -- Older Next.js versions (13.x) may set this
|
|
212
|
+
* 4. Falls back to `req.nextUrl.pathname` or raw URL pathname
|
|
213
|
+
*
|
|
214
|
+
* Query strings are stripped from the returned pattern.
|
|
215
|
+
*
|
|
216
|
+
* @param req - The incoming request
|
|
217
|
+
* @returns The route pattern string (e.g., '/api/users/[id]')
|
|
218
|
+
*/
|
|
219
|
+
function extractRoutePattern(req) {
|
|
220
|
+
try {
|
|
221
|
+
let pattern = null;
|
|
222
|
+
// Check Next.js internal headers in priority order
|
|
223
|
+
if (req.headers && typeof req.headers.get === 'function') {
|
|
224
|
+
pattern = req.headers.get('x-invoke-path');
|
|
225
|
+
if (!pattern) {
|
|
226
|
+
pattern = req.headers.get('x-matched-path');
|
|
227
|
+
}
|
|
228
|
+
if (!pattern) {
|
|
229
|
+
pattern = req.headers.get('x-nextjs-page');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Fallback to nextUrl.pathname or raw URL pathname
|
|
233
|
+
if (!pattern) {
|
|
234
|
+
if (req.nextUrl?.pathname) {
|
|
235
|
+
pattern = req.nextUrl.pathname;
|
|
236
|
+
}
|
|
237
|
+
else if (req.url) {
|
|
238
|
+
try {
|
|
239
|
+
const url = new URL(req.url, 'http://localhost');
|
|
240
|
+
pattern = url.pathname;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
pattern = req.url;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
pattern = '/';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Strip query strings if present
|
|
251
|
+
const queryIndex = pattern.indexOf('?');
|
|
252
|
+
if (queryIndex !== -1) {
|
|
253
|
+
pattern = pattern.substring(0, queryIndex);
|
|
254
|
+
}
|
|
255
|
+
return pattern;
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Fallback: return root path on any error
|
|
259
|
+
return '/';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// withTracing()
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
/**
|
|
266
|
+
* Wrap a Next.js API route handler with tracing.
|
|
267
|
+
*
|
|
268
|
+
* Creates a server span wrapping the handler execution. The span is named
|
|
269
|
+
* `{METHOD} {routePattern}` (e.g., `GET /api/users/[id]`) and includes
|
|
270
|
+
* Next.js-specific attributes.
|
|
271
|
+
*
|
|
272
|
+
* If the SDK is not initialized, the handler is called directly (no-op pass-through).
|
|
273
|
+
* Never throws -- all instrumentation errors are caught silently.
|
|
274
|
+
*
|
|
275
|
+
* @param handler - The route handler function
|
|
276
|
+
* @returns A wrapped handler with automatic tracing
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```typescript
|
|
280
|
+
* // app/api/users/[id]/route.ts
|
|
281
|
+
* import { withTracing } from '@justanalyticsapp/node/next';
|
|
282
|
+
*
|
|
283
|
+
* export const GET = withTracing(async (req, { params }) => {
|
|
284
|
+
* const user = await db.users.findUnique({ where: { id: params.id } });
|
|
285
|
+
* return Response.json(user);
|
|
286
|
+
* });
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
function withTracing(handler) {
|
|
290
|
+
return async function tracedHandler(req, context) {
|
|
291
|
+
// Pass through if SDK is not initialized
|
|
292
|
+
const client = getClient();
|
|
293
|
+
if (!client || !client.isInitialized()) {
|
|
294
|
+
try {
|
|
295
|
+
return await handler(req, context);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
throw err;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
// Extract route pattern and method
|
|
303
|
+
const routePattern = extractRoutePattern(req);
|
|
304
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
305
|
+
const spanName = `${method} ${routePattern}`;
|
|
306
|
+
const runtime = typeof process !== 'undefined'
|
|
307
|
+
? (process.env.NEXT_RUNTIME || 'nodejs')
|
|
308
|
+
: 'nodejs';
|
|
309
|
+
return await client.startSpan(spanName, { kind: 'server' }, async (span) => {
|
|
310
|
+
// Set Next.js-specific attributes
|
|
311
|
+
span.setAttribute('next.route', routePattern);
|
|
312
|
+
span.setAttribute('next.runtime', runtime);
|
|
313
|
+
span.setAttribute('http.method', method);
|
|
314
|
+
span.setAttribute('http.url', req.url || '');
|
|
315
|
+
span.setAttribute('http.route', routePattern);
|
|
316
|
+
let response;
|
|
317
|
+
try {
|
|
318
|
+
response = await handler(req, context);
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
// Handler threw -- span will be auto-ended with error by startSpan
|
|
322
|
+
throw err;
|
|
323
|
+
}
|
|
324
|
+
// Set response attributes
|
|
325
|
+
const statusCode = response.status;
|
|
326
|
+
span.setAttribute('http.status_code', statusCode);
|
|
327
|
+
if (statusCode >= 500) {
|
|
328
|
+
span.setStatus('error', `HTTP ${statusCode}`);
|
|
329
|
+
}
|
|
330
|
+
return response;
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
// If it's a handler error, re-throw it (startSpan already re-throws)
|
|
335
|
+
// If it's an instrumentation error, fall through to original handler
|
|
336
|
+
if (err instanceof Error && err.message === '[JustAnalytics] startSpan() requires a callback function.') {
|
|
337
|
+
return await handler(req, context);
|
|
338
|
+
}
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
// withMiddlewareTracing()
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
/**
|
|
347
|
+
* Wrap Next.js middleware with tracing.
|
|
348
|
+
*
|
|
349
|
+
* Creates a span with operation name `middleware` and kind `'server'`.
|
|
350
|
+
* Captures the response status and any `x-middleware-rewrite` or
|
|
351
|
+
* `x-middleware-redirect` headers as span attributes.
|
|
352
|
+
*
|
|
353
|
+
* If the SDK is not initialized, the middleware is called directly (no-op pass-through).
|
|
354
|
+
* Never throws -- all instrumentation errors are caught silently.
|
|
355
|
+
*
|
|
356
|
+
* @param middleware - The middleware function
|
|
357
|
+
* @returns A wrapped middleware with automatic tracing
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```typescript
|
|
361
|
+
* // middleware.ts
|
|
362
|
+
* import { withMiddlewareTracing } from '@justanalyticsapp/node/next';
|
|
363
|
+
*
|
|
364
|
+
* export default withMiddlewareTracing(async (req) => {
|
|
365
|
+
* // your middleware logic
|
|
366
|
+
* return NextResponse.next();
|
|
367
|
+
* });
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
function withMiddlewareTracing(middleware) {
|
|
371
|
+
return async function tracedMiddleware(req) {
|
|
372
|
+
// Pass through if SDK is not initialized
|
|
373
|
+
const client = getClient();
|
|
374
|
+
if (!client || !client.isInitialized()) {
|
|
375
|
+
try {
|
|
376
|
+
return await middleware(req);
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
throw err;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
return await client.startSpan('middleware', { kind: 'server' }, async (span) => {
|
|
384
|
+
// Set middleware-specific attributes
|
|
385
|
+
span.setAttribute('next.middleware', true);
|
|
386
|
+
let response;
|
|
387
|
+
try {
|
|
388
|
+
response = await middleware(req);
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
// Middleware threw -- span will be auto-ended with error by startSpan
|
|
392
|
+
throw err;
|
|
393
|
+
}
|
|
394
|
+
// Set response status
|
|
395
|
+
if (typeof response.status === 'number') {
|
|
396
|
+
span.setAttribute('http.status_code', response.status);
|
|
397
|
+
if (response.status >= 500) {
|
|
398
|
+
span.setStatus('error', `HTTP ${response.status}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// Capture middleware-specific headers
|
|
402
|
+
if (response.headers && typeof response.headers.get === 'function') {
|
|
403
|
+
const rewriteUrl = response.headers.get('x-middleware-rewrite');
|
|
404
|
+
if (rewriteUrl) {
|
|
405
|
+
span.setAttribute('next.middleware.rewrite', rewriteUrl);
|
|
406
|
+
}
|
|
407
|
+
const redirectUrl = response.headers.get('x-middleware-redirect');
|
|
408
|
+
if (redirectUrl) {
|
|
409
|
+
span.setAttribute('next.middleware.redirect', redirectUrl);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return response;
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
// If it's a middleware error, re-throw it (startSpan already re-throws)
|
|
417
|
+
if (err instanceof Error && err.message === '[JustAnalytics] startSpan() requires a callback function.') {
|
|
418
|
+
return await middleware(req);
|
|
419
|
+
}
|
|
420
|
+
throw err;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
// ---------------------------------------------------------------------------
|
|
425
|
+
// traceServerComponent()
|
|
426
|
+
// ---------------------------------------------------------------------------
|
|
427
|
+
/**
|
|
428
|
+
* Wrap an async Server Component render in a traced span.
|
|
429
|
+
*
|
|
430
|
+
* The span has kind `'internal'` and operation name `RSC {name}`
|
|
431
|
+
* (e.g., `RSC UserProfile`). It sets `next.rsc: true` to distinguish
|
|
432
|
+
* from API route spans.
|
|
433
|
+
*
|
|
434
|
+
* If the SDK is not initialized, the function is called directly (no-op pass-through).
|
|
435
|
+
* Never throws -- all instrumentation errors are caught silently.
|
|
436
|
+
*
|
|
437
|
+
* @param name - Component name for the span (e.g., 'UserProfile')
|
|
438
|
+
* @param fn - The async render function
|
|
439
|
+
* @returns The result of fn()
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```typescript
|
|
443
|
+
* // app/users/[id]/page.tsx
|
|
444
|
+
* import { traceServerComponent } from '@justanalyticsapp/node/next';
|
|
445
|
+
*
|
|
446
|
+
* export default async function UserPage({ params }) {
|
|
447
|
+
* return traceServerComponent('UserPage', async () => {
|
|
448
|
+
* const user = await getUser(params.id);
|
|
449
|
+
* return <UserProfile user={user} />;
|
|
450
|
+
* });
|
|
451
|
+
* }
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
function traceServerComponent(name, fn) {
|
|
455
|
+
// Pass through if SDK is not initialized
|
|
456
|
+
const client = getClient();
|
|
457
|
+
if (!client || !client.isInitialized()) {
|
|
458
|
+
try {
|
|
459
|
+
return fn();
|
|
460
|
+
}
|
|
461
|
+
catch (err) {
|
|
462
|
+
throw err;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
return client.startSpan(`RSC ${name}`, { kind: 'internal' }, (span) => {
|
|
467
|
+
// Set RSC-specific attribute
|
|
468
|
+
span.setAttribute('next.rsc', true);
|
|
469
|
+
return fn();
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
// If it's a user function error, re-throw it
|
|
474
|
+
if (err instanceof Error && err.message === '[JustAnalytics] startSpan() requires a callback function.') {
|
|
475
|
+
return fn();
|
|
476
|
+
}
|
|
477
|
+
throw err;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
//# sourceMappingURL=next.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.js","sourceRoot":"","sources":["../../src/integrations/next.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;;AA2HH,8CA4DC;AAkBD,4BAWC;AAoBD,kDA0CC;AA8BD,kCA+DC;AA8BD,sDAgEC;AAiCD,oDAgCC;AA1gBD,yEAAyE;AACzE,yEAAyE;AACzE,6DAA6D;AAC7D,sCAAgD;AAEhD;;;;;;;GAOG;AACH,IAAI,OAAO,GAA+B,IAAI,CAAC;AAE/C;;;;GAIG;AACH,SAAS,SAAS;IAChB,OAAO,OAAO,CAAC;AACjB,CAAC;AA2ED,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,iBAAiB,CAAC,MAAiC;IACjE,IAAI,CAAC;QACH,8DAA8D;QAC9D,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,MAAM,EAAE,CAAC;YAC1E,OAAO;QACT,CAAC;QAED,6BAA6B;QAC7B,MAAM,SAAS,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;QACjG,MAAM,SAAS,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;QACjG,MAAM,cAAc,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3G,MAAM,cAAc,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1G,MAAM,MAAM,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1F,MAAM,UAAU,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QAErF,+CAA+C;QAC/C,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,SAAS,IAAI,EAAE,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,SAAS,IAAI,EAAE,CAAC;QACjD,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,cAAc,IAAI,UAAU,CAAC;QACxE,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,cAAc,IAAI,UAAU,IAAI,aAAa,CAAC;QACzF,MAAM,SAAS,GAAG,MAAM,EAAE,SAAS,IAAI,MAAM,CAAC;QAE9C,2BAA2B;QAC3B,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CACV,oEAAoE,CACrE,CAAC;YACF,OAAO;QACT,CAAC;QAED,uCAAuC;QACvC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,IAAI,4BAAmB,EAAE,CAAC;QACtC,CAAC;QAED,wDAAwD;QACxD,MAAM,WAAW,GAAyB;YACxC,MAAM;YACN,MAAM;YACN,WAAW;YACX,WAAW;YACX,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,GAAG,CAAC,MAAM,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,GAAG,CAAC,MAAM,EAAE,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7F,GAAG,CAAC,MAAM,EAAE,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,MAAM,EAAE,8BAA8B,KAAK,SAAS;gBACtD,CAAC,CAAC,EAAE,8BAA8B,EAAE,MAAM,CAAC,8BAA8B,EAAE;gBAC3E,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,MAAM,EAAE,+BAA+B,KAAK,SAAS;gBACvD,CAAC,CAAC,EAAE,+BAA+B,EAAE,MAAM,CAAC,+BAA+B,EAAE;gBAC7E,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;QAEF,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,SAAgB,QAAQ;IACtB,IAAI,CAAC;QACH,qDAAqD;QACrD,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,MAAM,EAAE,CAAC;YAC1E,OAAO;QACT,CAAC;QAED,iBAAiB,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;IACvC,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E;;;;;;;;;;;;;GAaG;AACH,SAAgB,mBAAmB,CAAC,GAAoB;IACtD,IAAI,CAAC;QACH,IAAI,OAAO,GAAkB,IAAI,CAAC;QAElC,mDAAmD;QACnD,IAAI,GAAG,CAAC,OAAO,IAAI,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;YACzD,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC9C,CAAC;YACD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QAED,mDAAmD;QACnD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;gBAC1B,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC;YACjC,CAAC;iBAAM,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;gBACnB,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;oBACjD,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC;gBACzB,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC;gBACpB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,GAAG,GAAG,CAAC;YAChB,CAAC;QACH,CAAC;QAED,iCAAiC;QACjC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;YACtB,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QAC7C,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;QAC1C,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,WAAW,CAAC,OAAyB;IACnD,OAAO,KAAK,UAAU,aAAa,CACjC,GAAoB,EACpB,OAAwD;QAExD,yCAAyC;QACzC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC;YACvC,IAAI,CAAC;gBACH,OAAO,MAAM,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YACrC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,mCAAmC;YACnC,MAAM,YAAY,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,MAAM,IAAI,YAAY,EAAE,CAAC;YAC7C,MAAM,OAAO,GAAG,OAAO,OAAO,KAAK,WAAW;gBAC5C,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,QAAQ,CAAC;gBACxC,CAAC,CAAC,QAAQ,CAAC;YAEb,OAAO,MAAM,MAAM,CAAC,SAAS,CAC3B,QAAQ,EACR,EAAE,IAAI,EAAE,QAAQ,EAAE,EAClB,KAAK,EAAE,IAAI,EAAE,EAAE;gBACb,kCAAkC;gBAClC,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;gBAC9C,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;gBAC3C,IAAI,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBACzC,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;gBAC7C,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;gBAE9C,IAAI,QAAkB,CAAC;gBACvB,IAAI,CAAC;oBACH,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACzC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,mEAAmE;oBACnE,MAAM,GAAG,CAAC;gBACZ,CAAC;gBAED,0BAA0B;gBAC1B,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;gBACnC,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;gBAElD,IAAI,UAAU,IAAI,GAAG,EAAE,CAAC;oBACtB,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,UAAU,EAAE,CAAC,CAAC;gBAChD,CAAC;gBAED,OAAO,QAAQ,CAAC;YAClB,CAAC,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,qEAAqE;YACrE,qEAAqE;YACrE,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,OAAO,KAAK,2DAA2D,EAAE,CAAC;gBACxG,OAAO,MAAM,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YACrC,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,qBAAqB,CACnC,UAAkC;IAElC,OAAO,KAAK,UAAU,gBAAgB,CACpC,GAAoB;QAEpB,yCAAyC;QACzC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC;YACvC,IAAI,CAAC;gBACH,OAAO,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,SAAS,CAC3B,YAAY,EACZ,EAAE,IAAI,EAAE,QAAQ,EAAE,EAClB,KAAK,EAAE,IAAI,EAAE,EAAE;gBACb,qCAAqC;gBACrC,IAAI,CAAC,YAAY,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;gBAE3C,IAAI,QAAqC,CAAC;gBAC1C,IAAI,CAAC;oBACH,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;gBACnC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,sEAAsE;oBACtE,MAAM,GAAG,CAAC;gBACZ,CAAC;gBAED,sBAAsB;gBACtB,IAAI,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;oBACxC,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;oBACvD,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;wBAC3B,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;oBACrD,CAAC;gBACH,CAAC;gBAED,sCAAsC;gBACtC,IAAI,QAAQ,CAAC,OAAO,IAAI,OAAO,QAAQ,CAAC,OAAO,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;oBACnE,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;oBAChE,IAAI,UAAU,EAAE,CAAC;wBACf,IAAI,CAAC,YAAY,CAAC,yBAAyB,EAAE,UAAU,CAAC,CAAC;oBAC3D,CAAC;oBAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;oBAClE,IAAI,WAAW,EAAE,CAAC;wBAChB,IAAI,CAAC,YAAY,CAAC,0BAA0B,EAAE,WAAW,CAAC,CAAC;oBAC7D,CAAC;gBACH,CAAC;gBAED,OAAO,QAAQ,CAAC;YAClB,CAAC,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wEAAwE;YACxE,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,OAAO,KAAK,2DAA2D,EAAE,CAAC;gBACxG,OAAO,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;YAC/B,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAgB,oBAAoB,CAClC,IAAY,EACZ,EAA8B;IAE9B,yCAAyC;IACzC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC;YACH,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,SAAS,CACrB,OAAO,IAAI,EAAE,EACb,EAAE,IAAI,EAAE,UAAU,EAAE,EACpB,CAAC,IAAI,EAAE,EAAE;YACP,6BAA6B;YAC7B,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAEpC,OAAO,EAAE,EAAE,CAAC;QACd,CAAC,CACF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,6CAA6C;QAC7C,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,OAAO,KAAK,2DAA2D,EAAE,CAAC;YACxG,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file packages/node-sdk/src/integrations/pg.ts
|
|
3
|
+
* @description PostgreSQL auto-instrumentation integration for the JustAnalytics Node.js SDK.
|
|
4
|
+
*
|
|
5
|
+
* Implements Story 039 - PostgreSQL Auto-Instrumentation
|
|
6
|
+
*
|
|
7
|
+
* Monkey-patches `pg.Client.prototype.query`, `pg.Pool.prototype.query`, and
|
|
8
|
+
* `pg.Pool.prototype.connect` to automatically create `db.query` and `db.connect`
|
|
9
|
+
* spans for all PostgreSQL operations. This captures all PostgreSQL traffic regardless
|
|
10
|
+
* of which ORM or query builder sits on top (Prisma, Knex, TypeORM, Sequelize, Drizzle).
|
|
11
|
+
*
|
|
12
|
+
* Follows the same monkey-patching pattern established in Story 036 (HTTP Auto-Instrumentation):
|
|
13
|
+
* - Integration class with `enable()`/`disable()` lifecycle methods
|
|
14
|
+
* - Original function preservation for clean teardown
|
|
15
|
+
* - Integration with AsyncLocalStorage context for automatic parent-child span relationships
|
|
16
|
+
* - Fail-open design: if instrumentation code fails, the original query executes normally
|
|
17
|
+
*
|
|
18
|
+
* SQL sanitization is a critical security requirement: all literal values (strings,
|
|
19
|
+
* numbers, booleans) are replaced with `$?` placeholders. Actual query parameter
|
|
20
|
+
* values are NEVER included in span attributes.
|
|
21
|
+
*
|
|
22
|
+
* References:
|
|
23
|
+
* - OpenTelemetry Database Semantic Conventions (db.system, db.statement, etc.)
|
|
24
|
+
* - Story 035 - Node.js SDK Core (Span, context, transport)
|
|
25
|
+
* - Story 036 - HTTP Auto-Instrumentation (integration pattern)
|
|
26
|
+
*/
|
|
27
|
+
import { Span } from '../span';
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for the PostgreSQL auto-instrumentation integration.
|
|
30
|
+
*/
|
|
31
|
+
export interface PgIntegrationOptions {
|
|
32
|
+
/** Enable/disable the integration (default: true) */
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Maximum character length for the db.statement attribute.
|
|
36
|
+
* SQL queries longer than this are truncated with '...' suffix.
|
|
37
|
+
* (default: 2048)
|
|
38
|
+
*/
|
|
39
|
+
maxQueryLength?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Sanitize a SQL query by replacing literal values with placeholders.
|
|
43
|
+
*
|
|
44
|
+
* Rules (applied in order):
|
|
45
|
+
* 1. Strip block comments (/* ... */)
|
|
46
|
+
* 2. Strip line comments (-- ...)
|
|
47
|
+
* 3. Replace string literals ('...') with $?
|
|
48
|
+
* 4. Replace numeric literals with $? (preserving $N parameter placeholders)
|
|
49
|
+
* 5. Replace boolean literals (TRUE/FALSE) with $?
|
|
50
|
+
* 6. Normalize whitespace (collapse multiple spaces, trim)
|
|
51
|
+
* 7. Truncate to maxLength
|
|
52
|
+
*
|
|
53
|
+
* @param sql - Raw SQL query string
|
|
54
|
+
* @param maxLength - Maximum length for the sanitized output (default: 2048)
|
|
55
|
+
* @returns Sanitized SQL with literals replaced
|
|
56
|
+
*/
|
|
57
|
+
export declare function sanitizeSql(sql: string, maxLength?: number): string;
|
|
58
|
+
/**
|
|
59
|
+
* Extract the SQL operation keyword from a query string.
|
|
60
|
+
*
|
|
61
|
+
* Handles: SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP,
|
|
62
|
+
* BEGIN, COMMIT, ROLLBACK, TRUNCATE, COPY, EXPLAIN, ANALYZE, WITH (CTE)
|
|
63
|
+
*
|
|
64
|
+
* @param sql - Raw or sanitized SQL query
|
|
65
|
+
* @returns Uppercase operation keyword (e.g., "SELECT"), or "UNKNOWN" if not recognized
|
|
66
|
+
*/
|
|
67
|
+
export declare function extractSqlOperation(sql: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Extract the primary table name from a SQL query (best effort).
|
|
70
|
+
*
|
|
71
|
+
* Patterns handled:
|
|
72
|
+
* - SELECT ... FROM table_name
|
|
73
|
+
* - INSERT INTO table_name
|
|
74
|
+
* - UPDATE table_name
|
|
75
|
+
* - DELETE FROM table_name
|
|
76
|
+
* - Handles schema-qualified names: schema.table
|
|
77
|
+
* - Handles quoted identifiers: "table_name"
|
|
78
|
+
*
|
|
79
|
+
* @param sql - Raw or sanitized SQL query
|
|
80
|
+
* @returns Table name, or null if extraction fails
|
|
81
|
+
*/
|
|
82
|
+
export declare function extractTableName(sql: string): string | null;
|
|
83
|
+
/**
|
|
84
|
+
* Build a sanitized connection string from pg client config.
|
|
85
|
+
* Replaces password with '***'.
|
|
86
|
+
*
|
|
87
|
+
* @param connStr - Raw connection string potentially containing a password
|
|
88
|
+
* @returns Sanitized connection string with password masked
|
|
89
|
+
*/
|
|
90
|
+
export declare function sanitizeConnectionString(connStr: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* PgIntegration monkey-patches pg.Client.prototype.query,
|
|
93
|
+
* pg.Pool.prototype.query, and pg.Pool.prototype.connect to auto-create
|
|
94
|
+
* db.query and db.connect spans for all PostgreSQL operations.
|
|
95
|
+
*
|
|
96
|
+
* Follows the same pattern as HttpIntegration (Story 036):
|
|
97
|
+
* - Constructor accepts serviceName, options, onSpanEnd callback
|
|
98
|
+
* - enable() patches, disable() restores
|
|
99
|
+
* - Original functions preserved for clean teardown
|
|
100
|
+
* - Fail-open design: instrumentation failures never crash the host process
|
|
101
|
+
*/
|
|
102
|
+
export declare class PgIntegration {
|
|
103
|
+
private _enabled;
|
|
104
|
+
private _options;
|
|
105
|
+
private _serviceName;
|
|
106
|
+
private _onSpanEnd;
|
|
107
|
+
private _originalClientQuery;
|
|
108
|
+
private _originalPoolQuery;
|
|
109
|
+
private _originalPoolConnect;
|
|
110
|
+
private _pgClient;
|
|
111
|
+
private _pgPool;
|
|
112
|
+
/**
|
|
113
|
+
* Create a new PgIntegration.
|
|
114
|
+
*
|
|
115
|
+
* @param serviceName - The service name for span attribution
|
|
116
|
+
* @param options - Integration configuration options
|
|
117
|
+
* @param onSpanEnd - Callback invoked when a span ends (enqueues to transport)
|
|
118
|
+
*/
|
|
119
|
+
constructor(serviceName: string, options: PgIntegrationOptions | undefined, onSpanEnd: (span: Span) => void);
|
|
120
|
+
/**
|
|
121
|
+
* Activate the integration: load pg module and patch prototypes.
|
|
122
|
+
*
|
|
123
|
+
* Calling enable() when already enabled is a no-op (idempotent).
|
|
124
|
+
*/
|
|
125
|
+
enable(): void;
|
|
126
|
+
/**
|
|
127
|
+
* Deactivate: restore original pg functions.
|
|
128
|
+
*
|
|
129
|
+
* Calling disable() when not enabled is a no-op (idempotent).
|
|
130
|
+
*/
|
|
131
|
+
disable(): void;
|
|
132
|
+
/**
|
|
133
|
+
* Restore all patched functions to their originals.
|
|
134
|
+
*/
|
|
135
|
+
private _restoreOriginals;
|
|
136
|
+
/**
|
|
137
|
+
* Wrap pg.Client.prototype.query to create db.query spans.
|
|
138
|
+
*
|
|
139
|
+
* Handles all pg query signatures:
|
|
140
|
+
* 1. client.query(text, callback)
|
|
141
|
+
* 2. client.query(text, values, callback)
|
|
142
|
+
* 3. client.query(text) -- promise
|
|
143
|
+
* 4. client.query(text, values) -- promise
|
|
144
|
+
* 5. client.query(config) -- QueryConfig object, promise
|
|
145
|
+
* 6. client.query(config, callback) -- QueryConfig object, callback
|
|
146
|
+
*
|
|
147
|
+
* @param original - The original query function
|
|
148
|
+
* @returns A wrapped version of the query function
|
|
149
|
+
*/
|
|
150
|
+
private _wrapClientQuery;
|
|
151
|
+
/**
|
|
152
|
+
* Wrap pg.Pool.prototype.query to create db.query spans.
|
|
153
|
+
*
|
|
154
|
+
* Pool.query is a convenience method that acquires a client, runs the query,
|
|
155
|
+
* and releases the client. We patch it separately to track pool-level queries.
|
|
156
|
+
*
|
|
157
|
+
* @param original - The original Pool.prototype.query function
|
|
158
|
+
* @returns A wrapped version of the query function
|
|
159
|
+
*/
|
|
160
|
+
private _wrapPoolQuery;
|
|
161
|
+
/**
|
|
162
|
+
* Wrap pg.Pool.prototype.connect to create db.connect spans that measure
|
|
163
|
+
* the time to acquire a connection from the pool.
|
|
164
|
+
*
|
|
165
|
+
* @param original - The original Pool.prototype.connect function
|
|
166
|
+
* @returns A wrapped version of the connect function
|
|
167
|
+
*/
|
|
168
|
+
private _wrapPoolConnect;
|
|
169
|
+
}
|