@dex-monit/observability-sdk-node 1.0.10 → 1.0.12

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.
@@ -0,0 +1,78 @@
1
+ import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ import { Request, Response } from 'express';
4
+ import { MonitoringClient } from './monitoring-client.js';
5
+ export declare const HTTP_TRACE_MONITORING_TOKEN = "HTTP_TRACE_MONITORING_CLIENT";
6
+ export interface HttpTrace {
7
+ id: string;
8
+ timestamp: string;
9
+ method: string;
10
+ url: string;
11
+ path: string;
12
+ statusCode: number;
13
+ duration: number;
14
+ ip: string;
15
+ userAgent?: string;
16
+ referer?: string;
17
+ contentType?: string;
18
+ contentLength?: number;
19
+ requestId?: string;
20
+ transactionId?: string;
21
+ userId?: string;
22
+ error?: string;
23
+ query?: Record<string, unknown>;
24
+ params?: Record<string, unknown>;
25
+ responseSize?: number;
26
+ }
27
+ /**
28
+ * Get all captured HTTP traces
29
+ */
30
+ export declare function getHttpTraces(): HttpTrace[];
31
+ /**
32
+ * Get traces filtered by criteria
33
+ */
34
+ export declare function filterTraces(options: {
35
+ method?: string;
36
+ statusCode?: number;
37
+ minDuration?: number;
38
+ path?: string;
39
+ since?: Date;
40
+ }): HttpTrace[];
41
+ /**
42
+ * Clear all traces
43
+ */
44
+ export declare function clearTraces(): void;
45
+ /**
46
+ * Get trace statistics
47
+ */
48
+ export declare function getTraceStats(): {
49
+ total: number;
50
+ byMethod: Record<string, number>;
51
+ byStatus: Record<string, number>;
52
+ avgDuration: number;
53
+ slowestEndpoints: Array<{
54
+ path: string;
55
+ avgDuration: number;
56
+ count: number;
57
+ }>;
58
+ errorRate: number;
59
+ };
60
+ /**
61
+ * HTTP Interceptor for capturing all requests
62
+ */
63
+ export declare class HttpTraceInterceptor implements NestInterceptor {
64
+ private monitoringClient?;
65
+ constructor(monitoringClient?: MonitoringClient);
66
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
67
+ private recordTrace;
68
+ private getLevel;
69
+ private getClientIp;
70
+ private getResponseSize;
71
+ private sendTraceToMonitoring;
72
+ }
73
+ /**
74
+ * Middleware to capture HTTP requests (alternative to interceptor)
75
+ * Use this if you need to capture requests before NestJS routing
76
+ */
77
+ export declare function createHttpTraceMiddleware(): (req: Request, res: Response, next: () => void) => void;
78
+ //# sourceMappingURL=http-interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-interceptor.d.ts","sourceRoot":"","sources":["../../src/lib/http-interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,gBAAgB,EAChB,WAAW,EACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAElC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAiB,MAAM,wBAAwB,CAAC;AAKzE,eAAO,MAAM,2BAA2B,iCAAiC,CAAC;AAE1E,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAOD;;GAEG;AACH,wBAAgB,aAAa,IAAI,SAAS,EAAE,CAI3C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,IAAI,CAAC;CACd,GAAG,SAAS,EAAE,CAYd;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9E,SAAS,EAAE,MAAM,CAAC;CACnB,CAmEA;AAED;;GAEG;AACH,qBACa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,gBAAgB,CAAC,CAAmB;gBAEhC,gBAAgB,CAAC,EAAE,gBAAgB;IAI/C,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;IAyE5E,OAAO,CAAC,WAAW;IAoCnB,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,eAAe;YAWT,qBAAqB;CA0CpC;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,KAC/B,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,MAAM,IAAI,UA6EtD"}
@@ -0,0 +1,368 @@
1
+ import { __esDecorate, __runInitializers } from "tslib";
2
+ import { Injectable, } from '@nestjs/common';
3
+ import { tap, catchError } from 'rxjs/operators';
4
+ import { addBreadcrumb } from './monitoring-client.js';
5
+ import { RequestContextService } from '@dex-monit/observability-request-context';
6
+ // Token for injecting monitoring client
7
+ export const HTTP_TRACE_MONITORING_TOKEN = 'HTTP_TRACE_MONITORING_CLIENT';
8
+ // In-memory storage for recent traces (circular buffer)
9
+ const MAX_TRACES = 1000;
10
+ let traces = [];
11
+ let traceIndex = 0;
12
+ /**
13
+ * Get all captured HTTP traces
14
+ */
15
+ export function getHttpTraces() {
16
+ return [...traces].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
17
+ }
18
+ /**
19
+ * Get traces filtered by criteria
20
+ */
21
+ export function filterTraces(options) {
22
+ return getHttpTraces().filter((trace) => {
23
+ if (options.method && trace.method !== options.method)
24
+ return false;
25
+ if (options.statusCode && trace.statusCode !== options.statusCode)
26
+ return false;
27
+ if (options.minDuration && trace.duration < options.minDuration)
28
+ return false;
29
+ if (options.path && !trace.path.includes(options.path))
30
+ return false;
31
+ if (options.since && new Date(trace.timestamp) < options.since)
32
+ return false;
33
+ return true;
34
+ });
35
+ }
36
+ /**
37
+ * Clear all traces
38
+ */
39
+ export function clearTraces() {
40
+ traces = [];
41
+ traceIndex = 0;
42
+ }
43
+ /**
44
+ * Get trace statistics
45
+ */
46
+ export function getTraceStats() {
47
+ const allTraces = getHttpTraces();
48
+ const total = allTraces.length;
49
+ if (total === 0) {
50
+ return {
51
+ total: 0,
52
+ byMethod: {},
53
+ byStatus: {},
54
+ avgDuration: 0,
55
+ slowestEndpoints: [],
56
+ errorRate: 0,
57
+ };
58
+ }
59
+ const byMethod = {};
60
+ const byStatus = {};
61
+ const endpointStats = {};
62
+ let totalDuration = 0;
63
+ let errorCount = 0;
64
+ for (const trace of allTraces) {
65
+ // By method
66
+ byMethod[trace.method] = (byMethod[trace.method] || 0) + 1;
67
+ // By status category
68
+ const statusCategory = `${Math.floor(trace.statusCode / 100)}xx`;
69
+ byStatus[statusCategory] = (byStatus[statusCategory] || 0) + 1;
70
+ // Duration
71
+ totalDuration += trace.duration;
72
+ // Errors
73
+ if (trace.statusCode >= 400) {
74
+ errorCount++;
75
+ }
76
+ // Endpoint stats
77
+ const key = `${trace.method} ${trace.path}`;
78
+ if (!endpointStats[key]) {
79
+ endpointStats[key] = { totalDuration: 0, count: 0 };
80
+ }
81
+ endpointStats[key].totalDuration += trace.duration;
82
+ endpointStats[key].count++;
83
+ }
84
+ // Calculate slowest endpoints
85
+ const slowestEndpoints = Object.entries(endpointStats)
86
+ .map(([path, stats]) => ({
87
+ path,
88
+ avgDuration: Math.round(stats.totalDuration / stats.count),
89
+ count: stats.count,
90
+ }))
91
+ .sort((a, b) => b.avgDuration - a.avgDuration)
92
+ .slice(0, 10);
93
+ return {
94
+ total,
95
+ byMethod,
96
+ byStatus,
97
+ avgDuration: Math.round(totalDuration / total),
98
+ slowestEndpoints,
99
+ errorRate: Math.round((errorCount / total) * 100),
100
+ };
101
+ }
102
+ /**
103
+ * HTTP Interceptor for capturing all requests
104
+ */
105
+ let HttpTraceInterceptor = (() => {
106
+ let _classDecorators = [Injectable()];
107
+ let _classDescriptor;
108
+ let _classExtraInitializers = [];
109
+ let _classThis;
110
+ var HttpTraceInterceptor = class {
111
+ static { _classThis = this; }
112
+ static {
113
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
114
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
115
+ HttpTraceInterceptor = _classThis = _classDescriptor.value;
116
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
117
+ __runInitializers(_classThis, _classExtraInitializers);
118
+ }
119
+ monitoringClient;
120
+ constructor(monitoringClient) {
121
+ this.monitoringClient = monitoringClient;
122
+ }
123
+ intercept(context, next) {
124
+ if (context.getType() !== 'http') {
125
+ return next.handle();
126
+ }
127
+ const startTime = Date.now();
128
+ const http = context.switchToHttp();
129
+ const request = http.getRequest();
130
+ const response = http.getResponse();
131
+ const requestContext = RequestContextService.get();
132
+ // Extract request info
133
+ const method = request.method;
134
+ const url = request.originalUrl || request.url;
135
+ const path = request.path || url.split('?')[0];
136
+ const ip = this.getClientIp(request);
137
+ const userAgent = request.headers['user-agent'];
138
+ const referer = request.headers['referer'];
139
+ const contentType = request.headers['content-type'];
140
+ const contentLength = request.headers['content-length']
141
+ ? parseInt(request.headers['content-length'], 10)
142
+ : undefined;
143
+ return next.handle().pipe(tap((responseBody) => {
144
+ const duration = Date.now() - startTime;
145
+ const statusCode = response.statusCode;
146
+ this.recordTrace({
147
+ method,
148
+ url,
149
+ path,
150
+ statusCode,
151
+ duration,
152
+ ip,
153
+ userAgent,
154
+ referer,
155
+ contentType,
156
+ contentLength,
157
+ requestId: requestContext?.requestId,
158
+ transactionId: requestContext?.transactionId,
159
+ query: request.query,
160
+ params: request.params,
161
+ responseSize: this.getResponseSize(responseBody),
162
+ });
163
+ }), catchError((error) => {
164
+ const duration = Date.now() - startTime;
165
+ const statusCode = error.status || error.statusCode || 500;
166
+ this.recordTrace({
167
+ method,
168
+ url,
169
+ path,
170
+ statusCode,
171
+ duration,
172
+ ip,
173
+ userAgent,
174
+ referer,
175
+ contentType,
176
+ contentLength,
177
+ requestId: requestContext?.requestId,
178
+ transactionId: requestContext?.transactionId,
179
+ query: request.query,
180
+ params: request.params,
181
+ error: error.message,
182
+ });
183
+ throw error;
184
+ }));
185
+ }
186
+ recordTrace(data) {
187
+ const trace = {
188
+ id: `trace-${Date.now()}-${Math.random().toString(36).substring(7)}`,
189
+ timestamp: new Date().toISOString(),
190
+ ...data,
191
+ };
192
+ // Store in circular buffer
193
+ if (traces.length < MAX_TRACES) {
194
+ traces.push(trace);
195
+ }
196
+ else {
197
+ traces[traceIndex] = trace;
198
+ traceIndex = (traceIndex + 1) % MAX_TRACES;
199
+ }
200
+ // Add breadcrumb
201
+ addBreadcrumb({
202
+ category: 'http',
203
+ type: 'http',
204
+ message: `${data.method} ${data.path}`,
205
+ level: this.getLevel(data.statusCode),
206
+ data: {
207
+ url: data.url,
208
+ method: data.method,
209
+ status_code: data.statusCode,
210
+ duration: data.duration,
211
+ reason: data.error,
212
+ },
213
+ });
214
+ // Send to monitoring if client is configured
215
+ if (this.monitoringClient?.isEnabled()) {
216
+ this.sendTraceToMonitoring(trace);
217
+ }
218
+ }
219
+ getLevel(statusCode) {
220
+ if (statusCode >= 500)
221
+ return 'error';
222
+ if (statusCode >= 400)
223
+ return 'warning';
224
+ return 'info';
225
+ }
226
+ getClientIp(request) {
227
+ const forwardedFor = request.headers['x-forwarded-for'];
228
+ if (forwardedFor) {
229
+ const ips = Array.isArray(forwardedFor)
230
+ ? forwardedFor[0]
231
+ : forwardedFor.split(',')[0];
232
+ return ips.trim();
233
+ }
234
+ const realIp = request.headers['x-real-ip'];
235
+ if (realIp) {
236
+ return Array.isArray(realIp) ? realIp[0] : realIp;
237
+ }
238
+ return request.ip || request.socket?.remoteAddress || 'unknown';
239
+ }
240
+ getResponseSize(body) {
241
+ if (!body)
242
+ return undefined;
243
+ try {
244
+ const str = typeof body === 'string' ? body : JSON.stringify(body);
245
+ return Buffer.byteLength(str, 'utf8');
246
+ }
247
+ catch {
248
+ return undefined;
249
+ }
250
+ }
251
+ async sendTraceToMonitoring(trace) {
252
+ // Send to dedicated traces endpoint
253
+ try {
254
+ const config = this.monitoringClient?.getConfig();
255
+ if (!config)
256
+ return;
257
+ const response = await fetch(`${config.apiUrl}/traces`, {
258
+ method: 'POST',
259
+ headers: {
260
+ 'Content-Type': 'application/json',
261
+ 'X-Dex-Key': config.apiKey,
262
+ },
263
+ body: JSON.stringify({
264
+ traceId: trace.id,
265
+ method: trace.method,
266
+ url: trace.url,
267
+ path: trace.path,
268
+ statusCode: trace.statusCode,
269
+ duration: trace.duration,
270
+ ip: trace.ip,
271
+ userAgent: trace.userAgent,
272
+ referer: trace.referer,
273
+ contentType: trace.contentType,
274
+ contentLength: trace.contentLength,
275
+ responseSize: trace.responseSize,
276
+ requestId: trace.requestId,
277
+ transactionId: trace.transactionId,
278
+ userId: trace.userId,
279
+ error: trace.error,
280
+ query: trace.query,
281
+ params: trace.params,
282
+ timestamp: trace.timestamp,
283
+ }),
284
+ });
285
+ if (!response.ok) {
286
+ console.error('[DEX SDK] Failed to send trace:', response.status);
287
+ }
288
+ }
289
+ catch {
290
+ // Silently fail
291
+ }
292
+ }
293
+ };
294
+ return HttpTraceInterceptor = _classThis;
295
+ })();
296
+ export { HttpTraceInterceptor };
297
+ /**
298
+ * Middleware to capture HTTP requests (alternative to interceptor)
299
+ * Use this if you need to capture requests before NestJS routing
300
+ */
301
+ export function createHttpTraceMiddleware() {
302
+ return (req, res, next) => {
303
+ const startTime = Date.now();
304
+ const method = req.method;
305
+ const url = req.originalUrl || req.url;
306
+ const path = req.path || url.split('?')[0];
307
+ // Get client IP
308
+ const forwardedFor = req.headers['x-forwarded-for'];
309
+ let ip = req.ip || req.socket?.remoteAddress || 'unknown';
310
+ if (forwardedFor) {
311
+ ip = Array.isArray(forwardedFor)
312
+ ? forwardedFor[0]
313
+ : forwardedFor.split(',')[0].trim();
314
+ }
315
+ // Capture response
316
+ const originalEnd = res.end.bind(res);
317
+ res.end = function (chunk, encoding, cb) {
318
+ const duration = Date.now() - startTime;
319
+ const requestContext = RequestContextService.get();
320
+ const trace = {
321
+ id: `trace-${Date.now()}-${Math.random().toString(36).substring(7)}`,
322
+ timestamp: new Date().toISOString(),
323
+ method,
324
+ url,
325
+ path,
326
+ statusCode: res.statusCode,
327
+ duration,
328
+ ip,
329
+ userAgent: req.headers['user-agent'],
330
+ referer: req.headers['referer'],
331
+ contentType: req.headers['content-type'],
332
+ requestId: requestContext?.requestId,
333
+ transactionId: requestContext?.transactionId,
334
+ query: req.query,
335
+ };
336
+ // Store trace
337
+ if (traces.length < MAX_TRACES) {
338
+ traces.push(trace);
339
+ }
340
+ else {
341
+ traces[traceIndex] = trace;
342
+ traceIndex = (traceIndex + 1) % MAX_TRACES;
343
+ }
344
+ // Add breadcrumb
345
+ addBreadcrumb({
346
+ category: 'http',
347
+ type: 'http',
348
+ message: `${method} ${path}`,
349
+ level: res.statusCode >= 500
350
+ ? 'error'
351
+ : res.statusCode >= 400
352
+ ? 'warning'
353
+ : 'info',
354
+ data: {
355
+ url,
356
+ method,
357
+ status_code: res.statusCode,
358
+ duration,
359
+ },
360
+ });
361
+ if (typeof encoding === 'function') {
362
+ return originalEnd(chunk, encoding);
363
+ }
364
+ return originalEnd(chunk, encoding, cb);
365
+ };
366
+ next();
367
+ };
368
+ }
@@ -95,6 +95,14 @@ export declare class MonitoringClient {
95
95
  * Get the configured project
96
96
  */
97
97
  getProject(): string;
98
+ /**
99
+ * Get the client configuration (for HTTP trace sending)
100
+ */
101
+ getConfig(): {
102
+ apiUrl: string;
103
+ apiKey: string;
104
+ project: string;
105
+ };
98
106
  }
99
107
  /**
100
108
  * Create a monitoring client instance
@@ -1 +1 @@
1
- {"version":3,"file":"monitoring-client.d.ts","sourceRoot":"","sources":["../../src/lib/monitoring-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"monitoring-client.d.ts","sourceRoot":"","sources":["../../src/lib/monitoring-client.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,70 @@
1
1
  import { v4 as uuidv4 } from 'uuid';
2
+ import * as fs from 'fs';
2
3
  import { RequestContextService } from '@dex-monit/observability-request-context';
3
- import { scrubSensitiveData } from '@dex-monit/observability-scrubber';
4
+ import { scrubSensitiveData, } from '@dex-monit/observability-scrubber';
5
+ // Cache for source files to avoid re-reading
6
+ const sourceFileCache = new Map();
7
+ const MAX_CACHE_SIZE = 100;
8
+ /**
9
+ * Read source file and return lines array
10
+ */
11
+ function readSourceFile(filename) {
12
+ // Check cache first
13
+ if (sourceFileCache.has(filename)) {
14
+ return sourceFileCache.get(filename) || null;
15
+ }
16
+ try {
17
+ // Skip node_modules and non-local files
18
+ if (filename.includes('node_modules') ||
19
+ filename.startsWith('node:') ||
20
+ !filename.startsWith('/')) {
21
+ return null;
22
+ }
23
+ // Check if file exists
24
+ if (!fs.existsSync(filename)) {
25
+ return null;
26
+ }
27
+ const content = fs.readFileSync(filename, 'utf-8');
28
+ const lines = content.split('\n');
29
+ // Manage cache size
30
+ if (sourceFileCache.size >= MAX_CACHE_SIZE) {
31
+ const firstKey = sourceFileCache.keys().next().value;
32
+ if (firstKey)
33
+ sourceFileCache.delete(firstKey);
34
+ }
35
+ sourceFileCache.set(filename, lines);
36
+ return lines;
37
+ }
38
+ catch {
39
+ sourceFileCache.set(filename, null);
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * Get source code context around a specific line
45
+ */
46
+ function getSourceContext(filename, lineno, contextLines = 5) {
47
+ const lines = readSourceFile(filename);
48
+ if (!lines || lineno <= 0)
49
+ return undefined;
50
+ const start = Math.max(0, lineno - contextLines - 1);
51
+ const end = Math.min(lines.length, lineno + contextLines);
52
+ // Format lines with line numbers
53
+ const context = [];
54
+ for (let i = start; i < end; i++) {
55
+ const lineNum = i + 1;
56
+ const prefix = lineNum === lineno ? '>' : ' ';
57
+ context.push(`${prefix} ${lineNum.toString().padStart(4)} | ${lines[i]}`);
58
+ }
59
+ return context;
60
+ }
4
61
  // Global breadcrumb storage
5
62
  const MAX_BREADCRUMBS = 100;
6
63
  let globalBreadcrumbs = [];
7
64
  /**
8
- * Parse stack trace into structured frames
65
+ * Parse stack trace into structured frames with source code context
9
66
  */
10
- function parseStackTrace(stack) {
67
+ function parseStackTrace(stack, includeContext = true) {
11
68
  if (!stack)
12
69
  return [];
13
70
  const lines = stack.split('\n').slice(1); // Skip first line (error message)
@@ -15,12 +72,22 @@ function parseStackTrace(stack) {
15
72
  for (const line of lines) {
16
73
  const match = line.match(/at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+))\)?/);
17
74
  if (match) {
18
- frames.push({
75
+ const filename = match[2] || '<unknown>';
76
+ const lineno = parseInt(match[3] || '0', 10);
77
+ const frame = {
19
78
  function: match[1] || '<anonymous>',
20
- filename: match[2] || '<unknown>',
21
- lineno: parseInt(match[3] || '0', 10),
79
+ filename,
80
+ lineno,
22
81
  colno: parseInt(match[4] || '0', 10),
23
- });
82
+ };
83
+ // Add source code context for app frames (not node_modules)
84
+ if (includeContext && !filename.includes('node_modules')) {
85
+ const context = getSourceContext(filename, lineno, 5);
86
+ if (context) {
87
+ frame.context = context;
88
+ }
89
+ }
90
+ frames.push(frame);
24
91
  }
25
92
  }
26
93
  return frames;
@@ -196,7 +263,9 @@ export class MonitoringClient {
196
263
  serverName: this.config.serverName,
197
264
  requestId: requestContext?.requestId,
198
265
  transactionId: requestContext?.transactionId,
199
- data: data ? scrubSensitiveData(data, this.config.scrubberOptions) : undefined,
266
+ data: data
267
+ ? scrubSensitiveData(data, this.config.scrubberOptions)
268
+ : undefined,
200
269
  tags,
201
270
  };
202
271
  try {
@@ -215,7 +284,7 @@ export class MonitoringClient {
215
284
  return;
216
285
  }
217
286
  const requestContext = RequestContextService.get();
218
- const logEvents = logs.map(log => ({
287
+ const logEvents = logs.map((log) => ({
219
288
  id: uuidv4(),
220
289
  timestamp: log.timestamp || new Date().toISOString(),
221
290
  level: log.level,
@@ -225,7 +294,9 @@ export class MonitoringClient {
225
294
  serverName: this.config.serverName,
226
295
  requestId: requestContext?.requestId,
227
296
  transactionId: requestContext?.transactionId,
228
- data: log.data ? scrubSensitiveData(log.data, this.config.scrubberOptions) : undefined,
297
+ data: log.data
298
+ ? scrubSensitiveData(log.data, this.config.scrubberOptions)
299
+ : undefined,
229
300
  tags: log.tags,
230
301
  }));
231
302
  try {
@@ -329,6 +400,16 @@ export class MonitoringClient {
329
400
  getProject() {
330
401
  return this.config.project;
331
402
  }
403
+ /**
404
+ * Get the client configuration (for HTTP trace sending)
405
+ */
406
+ getConfig() {
407
+ return {
408
+ apiUrl: this.config.apiUrl,
409
+ apiKey: this.config.apiKey,
410
+ project: this.config.project,
411
+ };
412
+ }
332
413
  }
333
414
  /**
334
415
  * Create a monitoring client instance
@@ -7,4 +7,5 @@ export * from './remote-logger.js';
7
7
  export * from './dex-logger.service.js';
8
8
  export * from './console-capture.js';
9
9
  export * from './nest-logger-capture.js';
10
+ export * from './http-interceptor.js';
10
11
  //# sourceMappingURL=sdk-node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-node.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-node.ts"],"names":[],"mappings":"AACA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"sdk-node.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-node.ts"],"names":[],"mappings":"AACA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC"}
@@ -8,3 +8,4 @@ export * from './remote-logger.js';
8
8
  export * from './dex-logger.service.js';
9
9
  export * from './console-capture.js';
10
10
  export * from './nest-logger-capture.js';
11
+ export * from './http-interceptor.js';
@@ -18,6 +18,8 @@ export interface SdkNodeModuleConfig {
18
18
  captureConsole?: boolean;
19
19
  /** Whether to capture NestJS native Logger (default: true) */
20
20
  captureNestLogger?: boolean;
21
+ /** Whether to capture HTTP requests (default: true) */
22
+ captureHttpRequests?: boolean;
21
23
  }
22
24
  export { LOGGER_TOKEN };
23
25
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-node.module.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-node.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEb,kBAAkB,EAClB,UAAU,EACV,eAAe,EAChB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EAEL,sBAAsB,EAEvB,MAAM,wBAAwB,CAAC;AAQhC,OAAO,EAEL,YAAY,EACZ,YAAY,EACb,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,2BAA2B;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,yFAAyF;IACzF,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,gEAAgE;IAChE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;;;;;;;GAWG;AACH,qBAEa,aAAc,YAAW,UAAU,EAAE,eAAe;IAC/D,OAAO,CAAC,MAAM,CAAC,YAAY,CAA6B;IAExD;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa;IAgG1D;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI;IAK7C;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;CAUvC"}
1
+ {"version":3,"file":"sdk-node.module.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-node.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEb,kBAAkB,EAClB,UAAU,EACV,eAAe,EAChB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EAEL,sBAAsB,EAEvB,MAAM,wBAAwB,CAAC;AAShC,OAAO,EAEL,YAAY,EACZ,YAAY,EACb,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,2BAA2B;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,yFAAyF;IACzF,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,gEAAgE;IAChE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;;;;;;;GAWG;AACH,qBAEa,aAAc,YAAW,UAAU,EAAE,eAAe;IAC/D,OAAO,CAAC,MAAM,CAAC,YAAY,CAA6B;IAExD;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa;IA2G1D;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI;IAK7C;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;CAUvC"}