@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.
- package/dist/lib/error-capture.interceptor.d.ts +3 -0
- package/dist/lib/error-capture.interceptor.d.ts.map +1 -1
- package/dist/lib/error-capture.interceptor.js +69 -3
- package/dist/lib/monitoring-client.d.ts +26 -1
- package/dist/lib/monitoring-client.d.ts.map +1 -1
- package/dist/lib/monitoring-client.js +141 -10
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -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;
|
|
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
|
|
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] = '[
|
|
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":"
|
|
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
|
-
*
|
|
9
|
+
* Read source file and return lines array
|
|
6
10
|
*/
|
|
7
|
-
function
|
|
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
|
-
|
|
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
|
|
18
|
-
lineno
|
|
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
|
|
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
|
|
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
|
+
}
|