@dex-monit/observability-sdk-node 1.0.9 → 1.0.11

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.
@@ -13,6 +13,9 @@ export declare class ErrorCaptureInterceptor implements NestInterceptor {
13
13
  constructor(monitoringClient?: MonitoringClient | undefined);
14
14
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
15
15
  private captureError;
16
+ private getFullUrl;
16
17
  private sanitizeHeaders;
18
+ private sanitizeBody;
19
+ private sanitizeCookies;
17
20
  }
18
21
  //# sourceMappingURL=error-capture.interceptor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-capture.interceptor.d.ts","sourceRoot":"","sources":["../../src/lib/error-capture.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,gBAAgB,EAChB,WAAW,EACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAc,MAAM,MAAM,CAAC;AAG9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;GAMG;AACH,qBACa,uBAAwB,YAAW,eAAe;IAE3D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBAAjB,gBAAgB,CAAC,EAAE,gBAAgB,YAAA;IAGtD,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;IAY5E,OAAO,CAAC,YAAY;IAiCpB,OAAO,CAAC,eAAe;CAcxB"}
1
+ {"version":3,"file":"error-capture.interceptor.d.ts","sourceRoot":"","sources":["../../src/lib/error-capture.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,gBAAgB,EAChB,WAAW,EACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAc,MAAM,MAAM,CAAC;AAI9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;GAMG;AACH,qBACa,uBAAwB,YAAW,eAAe;IAE3D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBAAjB,gBAAgB,CAAC,EAAE,gBAAgB,YAAA;IAGtD,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;IAY5E,OAAO,CAAC,YAAY;IAkEpB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,eAAe;CASxB"}
@@ -2,6 +2,7 @@ import { __esDecorate, __runInitializers } from "tslib";
2
2
  import { Injectable, } from '@nestjs/common';
3
3
  import { throwError } from 'rxjs';
4
4
  import { catchError } from 'rxjs/operators';
5
+ import * as os from 'os';
5
6
  /**
6
7
  * Error Capture Interceptor
7
8
  *
@@ -46,11 +47,44 @@ let ErrorCaptureInterceptor = (() => {
46
47
  console.log('[DEX SDK] Sending error to monitoring...');
47
48
  this.monitoringClient
48
49
  .captureException(errorObj, {
50
+ // Full request context like Sentry
49
51
  request: {
50
- url: request?.url,
52
+ url: this.getFullUrl(request),
51
53
  method: request?.method,
52
54
  headers: this.sanitizeHeaders(request?.headers || {}),
53
55
  query: request?.query,
56
+ body: this.sanitizeBody(request?.body),
57
+ cookies: this.sanitizeCookies(request?.cookies),
58
+ },
59
+ // OS context
60
+ context: {
61
+ os: {
62
+ name: os.platform(),
63
+ version: os.release(),
64
+ kernelVersion: os.release(),
65
+ },
66
+ runtime: {
67
+ name: 'node',
68
+ version: process.version,
69
+ },
70
+ device: {
71
+ arch: os.arch(),
72
+ memory: os.totalmem(),
73
+ cpus: os.cpus().length,
74
+ hostname: os.hostname(),
75
+ },
76
+ app: {
77
+ startTime: process.uptime(),
78
+ memoryUsage: process.memoryUsage(),
79
+ },
80
+ },
81
+ // Tags for filtering
82
+ tags: {
83
+ 'http.method': request?.method || 'unknown',
84
+ 'http.status_code': '500',
85
+ 'runtime': 'node',
86
+ 'runtime.version': process.version,
87
+ 'os': os.platform(),
54
88
  },
55
89
  })
56
90
  .then(() => {
@@ -64,12 +98,20 @@ let ErrorCaptureInterceptor = (() => {
64
98
  console.log('[DEX SDK] No monitoring client configured');
65
99
  }
66
100
  }
101
+ getFullUrl(request) {
102
+ if (!request)
103
+ return 'unknown';
104
+ const protocol = request.protocol || 'http';
105
+ const host = request.get?.('host') || request.hostname || 'localhost';
106
+ const path = request.originalUrl || request.url || '/';
107
+ return `${protocol}://${host}${path}`;
108
+ }
67
109
  sanitizeHeaders(headers) {
68
- const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'x-dex-key'];
110
+ const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'x-dex-key', 'x-auth-token'];
69
111
  const sanitized = {};
70
112
  for (const [key, value] of Object.entries(headers)) {
71
113
  if (sensitiveHeaders.includes(key.toLowerCase())) {
72
- sanitized[key] = '[REDACTED]';
114
+ sanitized[key] = '[Filtered]';
73
115
  }
74
116
  else if (typeof value === 'string') {
75
117
  sanitized[key] = value;
@@ -77,6 +119,30 @@ let ErrorCaptureInterceptor = (() => {
77
119
  }
78
120
  return sanitized;
79
121
  }
122
+ sanitizeBody(body) {
123
+ if (!body || typeof body !== 'object')
124
+ return undefined;
125
+ const sensitiveKeys = ['password', 'token', 'secret', 'apiKey', 'api_key', 'credit_card', 'cvv'];
126
+ const sanitized = {};
127
+ for (const [key, value] of Object.entries(body)) {
128
+ if (sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase()))) {
129
+ sanitized[key] = '[Filtered]';
130
+ }
131
+ else {
132
+ sanitized[key] = value;
133
+ }
134
+ }
135
+ return sanitized;
136
+ }
137
+ sanitizeCookies(cookies) {
138
+ if (!cookies || typeof cookies !== 'object')
139
+ return undefined;
140
+ const sanitized = {};
141
+ for (const key of Object.keys(cookies)) {
142
+ sanitized[key] = '[Filtered]';
143
+ }
144
+ return sanitized;
145
+ }
80
146
  };
81
147
  return ErrorCaptureInterceptor = _classThis;
82
148
  })();
@@ -1,4 +1,4 @@
1
- import { ErrorEvent, EventContext, Severity } from '@dex-monit/observability-contracts';
1
+ import { ErrorEvent, EventContext, Severity, Breadcrumb } from '@dex-monit/observability-contracts';
2
2
  import { ScrubberOptions } from '@dex-monit/observability-scrubber';
3
3
  /**
4
4
  * Monitoring client configuration
@@ -100,4 +100,29 @@ export declare class MonitoringClient {
100
100
  * Create a monitoring client instance
101
101
  */
102
102
  export declare function createMonitoringClient(config: MonitoringClientConfig): MonitoringClient;
103
+ /**
104
+ * Add a breadcrumb to the global breadcrumb trail
105
+ */
106
+ export declare function addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;
107
+ /**
108
+ * Get current breadcrumbs
109
+ */
110
+ export declare function getBreadcrumbs(): Breadcrumb[];
111
+ /**
112
+ * Clear all breadcrumbs
113
+ */
114
+ export declare function clearBreadcrumbs(): void;
115
+ /**
116
+ * Add an HTTP breadcrumb (convenience function)
117
+ */
118
+ export declare function addHttpBreadcrumb(data: {
119
+ url: string;
120
+ method: string;
121
+ statusCode?: number;
122
+ duration?: number;
123
+ }): void;
124
+ /**
125
+ * Add a console breadcrumb (convenience function)
126
+ */
127
+ export declare function addConsoleBreadcrumb(level: Severity, message: string, data?: Record<string, unknown>): void;
103
128
  //# sourceMappingURL=monitoring-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"monitoring-client.d.ts","sourceRoot":"","sources":["../../src/lib/monitoring-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAwB,MAAM,oCAAoC,CAAC;AAE9G,OAAO,EAAsB,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAExF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,uDAAuD;IACvD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,UAAU,GAAG,IAAI,CAAC;CACvD;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAChC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,WAAW;IACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,sBAAsB;IACtB,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAClC,mBAAmB;IACnB,IAAI,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;CAC7B;AAyCD;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAmC;gBAErC,MAAM,EAAE,sBAAsB;IAe1C;;OAEG;IACG,gBAAgB,CACpB,KAAK,EAAE,KAAK,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAgEzB;;OAEG;IACG,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,QAAiB,EACxB,OAAO,GAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAM,GAC1C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAwDzB;;OAEG;IACG,UAAU,CACd,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC;IA6BhB;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC;QAC5B,KAAK,EAAE,QAAQ,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BlB;;OAEG;YACW,SAAS;IA6BvB;;OAEG;YACW,OAAO;IA4BrB;;OAEG;YACW,aAAa;IA4B3B;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,UAAU,IAAI,MAAM;CAGrB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,gBAAgB,CAElB"}
1
+ {"version":3,"file":"monitoring-client.d.ts","sourceRoot":"","sources":["../../src/lib/monitoring-client.ts"],"names":[],"mappings":"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"}
@@ -1,10 +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;
4
8
  /**
5
- * Parse stack trace into structured frames
9
+ * Read source file and return lines array
6
10
  */
7
- function parseStackTrace(stack) {
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
+ }
61
+ // Global breadcrumb storage
62
+ const MAX_BREADCRUMBS = 100;
63
+ let globalBreadcrumbs = [];
64
+ /**
65
+ * Parse stack trace into structured frames with source code context
66
+ */
67
+ function parseStackTrace(stack, includeContext = true) {
8
68
  if (!stack)
9
69
  return [];
10
70
  const lines = stack.split('\n').slice(1); // Skip first line (error message)
@@ -12,12 +72,22 @@ function parseStackTrace(stack) {
12
72
  for (const line of lines) {
13
73
  const match = line.match(/at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+))\)?/);
14
74
  if (match) {
15
- frames.push({
75
+ const filename = match[2] || '<unknown>';
76
+ const lineno = parseInt(match[3] || '0', 10);
77
+ const frame = {
16
78
  function: match[1] || '<anonymous>',
17
- filename: match[2] || '<unknown>',
18
- lineno: parseInt(match[3] || '0', 10),
79
+ filename,
80
+ lineno,
19
81
  colno: parseInt(match[4] || '0', 10),
20
- });
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);
21
91
  }
22
92
  }
23
93
  return frames;
@@ -86,6 +156,8 @@ export class MonitoringClient {
86
156
  value: error.message,
87
157
  stacktrace: parseStackTrace(error.stack),
88
158
  },
159
+ // Include breadcrumbs (events leading up to the error)
160
+ breadcrumbs: getBreadcrumbs(),
89
161
  requestId: requestContext?.requestId,
90
162
  transactionId: requestContext?.transactionId,
91
163
  fingerprint: options.fingerprint || generateFingerprint(error),
@@ -191,7 +263,9 @@ export class MonitoringClient {
191
263
  serverName: this.config.serverName,
192
264
  requestId: requestContext?.requestId,
193
265
  transactionId: requestContext?.transactionId,
194
- data: data ? scrubSensitiveData(data, this.config.scrubberOptions) : undefined,
266
+ data: data
267
+ ? scrubSensitiveData(data, this.config.scrubberOptions)
268
+ : undefined,
195
269
  tags,
196
270
  };
197
271
  try {
@@ -210,7 +284,7 @@ export class MonitoringClient {
210
284
  return;
211
285
  }
212
286
  const requestContext = RequestContextService.get();
213
- const logEvents = logs.map(log => ({
287
+ const logEvents = logs.map((log) => ({
214
288
  id: uuidv4(),
215
289
  timestamp: log.timestamp || new Date().toISOString(),
216
290
  level: log.level,
@@ -220,7 +294,9 @@ export class MonitoringClient {
220
294
  serverName: this.config.serverName,
221
295
  requestId: requestContext?.requestId,
222
296
  transactionId: requestContext?.transactionId,
223
- data: log.data ? scrubSensitiveData(log.data, this.config.scrubberOptions) : undefined,
297
+ data: log.data
298
+ ? scrubSensitiveData(log.data, this.config.scrubberOptions)
299
+ : undefined,
224
300
  tags: log.tags,
225
301
  }));
226
302
  try {
@@ -331,3 +407,58 @@ export class MonitoringClient {
331
407
  export function createMonitoringClient(config) {
332
408
  return new MonitoringClient(config);
333
409
  }
410
+ /**
411
+ * Add a breadcrumb to the global breadcrumb trail
412
+ */
413
+ export function addBreadcrumb(breadcrumb) {
414
+ const crumb = {
415
+ timestamp: new Date().toISOString(),
416
+ ...breadcrumb,
417
+ };
418
+ globalBreadcrumbs.push(crumb);
419
+ // Keep only the last MAX_BREADCRUMBS
420
+ if (globalBreadcrumbs.length > MAX_BREADCRUMBS) {
421
+ globalBreadcrumbs = globalBreadcrumbs.slice(-MAX_BREADCRUMBS);
422
+ }
423
+ }
424
+ /**
425
+ * Get current breadcrumbs
426
+ */
427
+ export function getBreadcrumbs() {
428
+ return [...globalBreadcrumbs];
429
+ }
430
+ /**
431
+ * Clear all breadcrumbs
432
+ */
433
+ export function clearBreadcrumbs() {
434
+ globalBreadcrumbs = [];
435
+ }
436
+ /**
437
+ * Add an HTTP breadcrumb (convenience function)
438
+ */
439
+ export function addHttpBreadcrumb(data) {
440
+ addBreadcrumb({
441
+ category: 'http',
442
+ type: 'http',
443
+ level: data.statusCode && data.statusCode >= 400 ? 'error' : 'info',
444
+ message: `${data.method} ${data.url}`,
445
+ data: {
446
+ url: data.url,
447
+ method: data.method,
448
+ status_code: data.statusCode,
449
+ duration_ms: data.duration,
450
+ },
451
+ });
452
+ }
453
+ /**
454
+ * Add a console breadcrumb (convenience function)
455
+ */
456
+ export function addConsoleBreadcrumb(level, message, data) {
457
+ addBreadcrumb({
458
+ category: 'console',
459
+ type: 'debug',
460
+ level,
461
+ message,
462
+ data,
463
+ });
464
+ }