@vantatrace/sdk 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/README.md +0 -0
- package/dist/context.d.ts +17 -0
- package/dist/context.js +57 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +156 -0
- package/dist/normalizer.d.ts +10 -0
- package/dist/normalizer.js +103 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +98 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +2 -0
- package/package.json +36 -0
- package/src/context.ts +22 -0
- package/src/index.ts +175 -0
- package/src/normalizer.ts +74 -0
- package/src/transport.ts +76 -0
- package/src/types.ts +52 -0
- package/tsconfig.json +15 -0
- package/vantatrace-0.1.0.tgz +0 -0
package/README.md
ADDED
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function getSystemContext(): {
|
|
2
|
+
nodeVersion: string;
|
|
3
|
+
hostname: string;
|
|
4
|
+
pid: number;
|
|
5
|
+
platform: NodeJS.Platform;
|
|
6
|
+
arch: string;
|
|
7
|
+
memory: {
|
|
8
|
+
rss: number;
|
|
9
|
+
heapTotal: number;
|
|
10
|
+
heapUsed: number;
|
|
11
|
+
external: number;
|
|
12
|
+
freeMem: number;
|
|
13
|
+
totalMem: number;
|
|
14
|
+
};
|
|
15
|
+
loadavg: number[];
|
|
16
|
+
uptime: number;
|
|
17
|
+
};
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getSystemContext = getSystemContext;
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
function getSystemContext() {
|
|
39
|
+
const memoryUsage = process.memoryUsage();
|
|
40
|
+
return {
|
|
41
|
+
nodeVersion: process.version,
|
|
42
|
+
hostname: os.hostname(),
|
|
43
|
+
pid: process.pid,
|
|
44
|
+
platform: os.platform(),
|
|
45
|
+
arch: os.arch(),
|
|
46
|
+
memory: {
|
|
47
|
+
rss: memoryUsage.rss,
|
|
48
|
+
heapTotal: memoryUsage.heapTotal,
|
|
49
|
+
heapUsed: memoryUsage.heapUsed,
|
|
50
|
+
external: memoryUsage.external,
|
|
51
|
+
freeMem: os.freemem(),
|
|
52
|
+
totalMem: os.totalmem()
|
|
53
|
+
},
|
|
54
|
+
loadavg: os.loadavg(),
|
|
55
|
+
uptime: Math.round(process.uptime())
|
|
56
|
+
};
|
|
57
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { VantaTraceOptions, VantaTraceContext } from './types';
|
|
2
|
+
export declare class VantaTrace {
|
|
3
|
+
private apiKey;
|
|
4
|
+
private serviceName;
|
|
5
|
+
private environment;
|
|
6
|
+
private debug;
|
|
7
|
+
private apiUrl;
|
|
8
|
+
constructor(options: VantaTraceOptions);
|
|
9
|
+
/**
|
|
10
|
+
* Primary method to capture exceptions and send them to VantaTrace
|
|
11
|
+
*/
|
|
12
|
+
captureException(error: any, context?: VantaTraceContext): void;
|
|
13
|
+
/**
|
|
14
|
+
* Helper method to capture critical errors
|
|
15
|
+
*/
|
|
16
|
+
captureCritical(error: any, context?: Omit<VantaTraceContext, 'severity'>): void;
|
|
17
|
+
/**
|
|
18
|
+
* Helper method to capture warning errors
|
|
19
|
+
*/
|
|
20
|
+
captureWarning(error: any, context?: Omit<VantaTraceContext, 'severity'>): void;
|
|
21
|
+
/**
|
|
22
|
+
* Helper method to capture info messages/errors
|
|
23
|
+
*/
|
|
24
|
+
captureInfo(error: any, context?: Omit<VantaTraceContext, 'severity'>): void;
|
|
25
|
+
/**
|
|
26
|
+
* Express error handling middleware
|
|
27
|
+
*/
|
|
28
|
+
expressMiddleware(): (err: any, req: any, res: any, next: any) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Automatically catch all uncaught exceptions and unhandled rejections
|
|
31
|
+
*/
|
|
32
|
+
initGlobalHandlers(): void;
|
|
33
|
+
}
|
|
34
|
+
export default VantaTrace;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VantaTrace = void 0;
|
|
4
|
+
const normalizer_1 = require("./normalizer");
|
|
5
|
+
const context_1 = require("./context");
|
|
6
|
+
const transport_1 = require("./transport");
|
|
7
|
+
class VantaTrace {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.apiKey = options.apiKey || '';
|
|
10
|
+
this.serviceName = options.serviceName || 'unknown-service';
|
|
11
|
+
let envInput = options.environment || process.env.NODE_ENV || 'development';
|
|
12
|
+
if (this.apiKey.includes('live')) {
|
|
13
|
+
envInput = 'live';
|
|
14
|
+
}
|
|
15
|
+
else if (this.apiKey.includes('test')) {
|
|
16
|
+
envInput = 'test';
|
|
17
|
+
}
|
|
18
|
+
this.environment = (envInput === 'production' || envInput === 'prod' || envInput === 'live') ? 'live' : 'test';
|
|
19
|
+
this.debug = !!options.debug;
|
|
20
|
+
// Default to localhost:6000/api/events (Standard Ingestion Endpoint)
|
|
21
|
+
this.apiUrl = options.apiUrl || 'https://api.vantatrace.com/api/events';
|
|
22
|
+
if (!options.apiKey && this.debug) {
|
|
23
|
+
console.warn('[VantaTrace] WARNING: API key is missing. SDK will run in dry-run mode.');
|
|
24
|
+
}
|
|
25
|
+
if (this.debug) {
|
|
26
|
+
console.log(`[VantaTrace] Initialized SDK for service "${this.serviceName}" on environment "${this.environment}".`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Primary method to capture exceptions and send them to VantaTrace
|
|
31
|
+
*/
|
|
32
|
+
captureException(error, context) {
|
|
33
|
+
try {
|
|
34
|
+
if (!this.apiKey) {
|
|
35
|
+
if (this.debug) {
|
|
36
|
+
console.log('[VantaTrace] Dry-run: captured exception:', error, 'Context:', context);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const normalized = (0, normalizer_1.normalizeError)(error);
|
|
41
|
+
const systemContext = (0, context_1.getSystemContext)();
|
|
42
|
+
const payload = {
|
|
43
|
+
apiKey: this.apiKey,
|
|
44
|
+
serviceName: this.serviceName,
|
|
45
|
+
environment: this.environment,
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
error: normalized,
|
|
48
|
+
context: context || {},
|
|
49
|
+
system: systemContext,
|
|
50
|
+
severity: context?.severity
|
|
51
|
+
};
|
|
52
|
+
if (this.debug) {
|
|
53
|
+
console.log(`[VantaTrace] Sending error event: ${normalized.name} - ${normalized.message} (Severity: ${payload.severity || 'default'})`);
|
|
54
|
+
}
|
|
55
|
+
(0, transport_1.sendPayload)(this.apiUrl, this.apiKey, payload, this.debug);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
if (this.debug) {
|
|
59
|
+
console.error(`[VantaTrace] Failed to capture exception internally: ${e.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Helper method to capture critical errors
|
|
65
|
+
*/
|
|
66
|
+
captureCritical(error, context) {
|
|
67
|
+
this.captureException(error, { ...context, severity: 'critical' });
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Helper method to capture warning errors
|
|
71
|
+
*/
|
|
72
|
+
captureWarning(error, context) {
|
|
73
|
+
this.captureException(error, { ...context, severity: 'warning' });
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Helper method to capture info messages/errors
|
|
77
|
+
*/
|
|
78
|
+
captureInfo(error, context) {
|
|
79
|
+
this.captureException(error, { ...context, severity: 'info' });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Express error handling middleware
|
|
83
|
+
*/
|
|
84
|
+
expressMiddleware() {
|
|
85
|
+
return (err, req, res, next) => {
|
|
86
|
+
let userId = undefined;
|
|
87
|
+
if (req.user && typeof req.user === 'object') {
|
|
88
|
+
userId = req.user.id || req.user._id || req.user.userId;
|
|
89
|
+
}
|
|
90
|
+
else if (req.userId) {
|
|
91
|
+
userId = req.userId;
|
|
92
|
+
}
|
|
93
|
+
// Sanitize request body if password exists
|
|
94
|
+
let sanitizedBody = undefined;
|
|
95
|
+
if (req.body && typeof req.body === 'object') {
|
|
96
|
+
sanitizedBody = { ...req.body };
|
|
97
|
+
if ('password' in sanitizedBody)
|
|
98
|
+
sanitizedBody.password = '[REDACTED]';
|
|
99
|
+
if ('token' in sanitizedBody)
|
|
100
|
+
sanitizedBody.token = '[REDACTED]';
|
|
101
|
+
}
|
|
102
|
+
// Extract client IP
|
|
103
|
+
const ip = req.ip || req.headers['x-forwarded-for'] || req.socket?.remoteAddress;
|
|
104
|
+
// Extract and sanitize headers
|
|
105
|
+
const sanitizedHeaders = {};
|
|
106
|
+
const sensitiveHeaderKeys = ['authorization', 'cookie', 'set-cookie', 'x-api-key', 'proxy-authorization'];
|
|
107
|
+
if (req.headers && typeof req.headers === 'object') {
|
|
108
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
109
|
+
if (sensitiveHeaderKeys.includes(key.toLowerCase())) {
|
|
110
|
+
sanitizedHeaders[key] = '[REDACTED]';
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
sanitizedHeaders[key] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.captureException(err, {
|
|
118
|
+
userId: userId ? String(userId) : undefined,
|
|
119
|
+
route: req.route?.path || req.path || req.url,
|
|
120
|
+
method: req.method,
|
|
121
|
+
ip: ip ? String(ip) : undefined,
|
|
122
|
+
headers: sanitizedHeaders,
|
|
123
|
+
metadata: {
|
|
124
|
+
query: req.query,
|
|
125
|
+
body: sanitizedBody
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
next(err);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Automatically catch all uncaught exceptions and unhandled rejections
|
|
133
|
+
*/
|
|
134
|
+
initGlobalHandlers() {
|
|
135
|
+
process.on('uncaughtException', (err) => {
|
|
136
|
+
if (this.debug) {
|
|
137
|
+
console.log('[VantaTrace] Captured uncaught exception globally');
|
|
138
|
+
}
|
|
139
|
+
this.captureException(err);
|
|
140
|
+
// Let the exception bubble up to avoid inconsistent process state (standard practice)
|
|
141
|
+
// but give a short time window for the async request to finish sending.
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}, 500);
|
|
145
|
+
});
|
|
146
|
+
process.on('unhandledRejection', (reason) => {
|
|
147
|
+
if (this.debug) {
|
|
148
|
+
console.log('[VantaTrace] Captured unhandled promise rejection globally');
|
|
149
|
+
}
|
|
150
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
151
|
+
this.captureException(err);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
exports.VantaTrace = VantaTrace;
|
|
156
|
+
exports.default = VantaTrace;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function getFingerprint(name: string, message: string, stack: string): string;
|
|
2
|
+
export declare function normalizeError(err: any): {
|
|
3
|
+
name: string;
|
|
4
|
+
message: string;
|
|
5
|
+
stack: string;
|
|
6
|
+
fingerprint: string;
|
|
7
|
+
code?: string;
|
|
8
|
+
statusCode?: number;
|
|
9
|
+
extra?: Record<string, any>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getFingerprint = getFingerprint;
|
|
37
|
+
exports.normalizeError = normalizeError;
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
39
|
+
function getFingerprint(name, message, stack) {
|
|
40
|
+
const hash = crypto.createHash('md5');
|
|
41
|
+
// Group by name, message, and the first 3 lines of the stack trace to ignore line number variations deep in libraries
|
|
42
|
+
const cleanStack = (stack || '')
|
|
43
|
+
.split('\n')
|
|
44
|
+
.slice(0, 4)
|
|
45
|
+
.map(line => line.trim())
|
|
46
|
+
.join('\n');
|
|
47
|
+
hash.update(`${name}:${message}:${cleanStack}`);
|
|
48
|
+
return hash.digest('hex');
|
|
49
|
+
}
|
|
50
|
+
function normalizeError(err) {
|
|
51
|
+
let name = 'Error';
|
|
52
|
+
let message = 'Unknown error';
|
|
53
|
+
let stack = '';
|
|
54
|
+
let code = undefined;
|
|
55
|
+
let statusCode = undefined;
|
|
56
|
+
let extra = undefined;
|
|
57
|
+
if (err instanceof Error) {
|
|
58
|
+
name = err.name || 'Error';
|
|
59
|
+
message = err.message || 'Unknown error';
|
|
60
|
+
stack = err.stack || '';
|
|
61
|
+
if ('code' in err)
|
|
62
|
+
code = String(err.code);
|
|
63
|
+
if ('status' in err)
|
|
64
|
+
statusCode = Number(err.status);
|
|
65
|
+
if ('statusCode' in err)
|
|
66
|
+
statusCode = Number(err.statusCode);
|
|
67
|
+
const extraKeys = Object.keys(err).filter(k => !['name', 'message', 'stack', 'code', 'status', 'statusCode'].includes(k));
|
|
68
|
+
if (extraKeys.length > 0) {
|
|
69
|
+
extra = {};
|
|
70
|
+
for (const key of extraKeys) {
|
|
71
|
+
extra[key] = err[key];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (typeof err === 'string') {
|
|
76
|
+
message = err;
|
|
77
|
+
stack = new Error(err).stack || '';
|
|
78
|
+
}
|
|
79
|
+
else if (err && typeof err === 'object') {
|
|
80
|
+
name = err.name || err.constructor?.name || 'Error';
|
|
81
|
+
message = err.message || JSON.stringify(err);
|
|
82
|
+
stack = err.stack || new Error(message).stack || '';
|
|
83
|
+
if ('code' in err)
|
|
84
|
+
code = String(err.code);
|
|
85
|
+
if ('status' in err)
|
|
86
|
+
statusCode = Number(err.status);
|
|
87
|
+
if ('statusCode' in err)
|
|
88
|
+
statusCode = Number(err.statusCode);
|
|
89
|
+
const extraKeys = Object.keys(err).filter(k => !['name', 'message', 'stack', 'code', 'status', 'statusCode'].includes(k));
|
|
90
|
+
if (extraKeys.length > 0) {
|
|
91
|
+
extra = {};
|
|
92
|
+
for (const key of extraKeys) {
|
|
93
|
+
extra[key] = err[key];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
message = String(err);
|
|
99
|
+
stack = new Error(message).stack || '';
|
|
100
|
+
}
|
|
101
|
+
const fingerprint = getFingerprint(name, message, stack);
|
|
102
|
+
return { name, message, stack, fingerprint, code, statusCode, extra };
|
|
103
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.sendPayload = sendPayload;
|
|
37
|
+
const http = __importStar(require("http"));
|
|
38
|
+
const https = __importStar(require("https"));
|
|
39
|
+
const url_1 = require("url");
|
|
40
|
+
function sendPayload(apiUrl, apiKey, payload, debug = false, retriesRemaining = 1) {
|
|
41
|
+
const logDebug = (msg) => {
|
|
42
|
+
if (debug) {
|
|
43
|
+
console.log(`[VantaTrace] ${msg}`);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// Run asynchronously to ensure we don't block the caller
|
|
47
|
+
process.nextTick(() => {
|
|
48
|
+
try {
|
|
49
|
+
const parsedUrl = new url_1.URL(apiUrl);
|
|
50
|
+
const postData = JSON.stringify(payload);
|
|
51
|
+
const options = {
|
|
52
|
+
hostname: parsedUrl.hostname,
|
|
53
|
+
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
|
54
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
59
|
+
'x-api-key': apiKey
|
|
60
|
+
},
|
|
61
|
+
timeout: 300 // 300ms max timeout
|
|
62
|
+
};
|
|
63
|
+
const client = parsedUrl.protocol === 'https:' ? https : http;
|
|
64
|
+
const req = client.request(options, (res) => {
|
|
65
|
+
// Consume response data to prevent resource leaks
|
|
66
|
+
res.on('data', () => { });
|
|
67
|
+
res.on('end', () => {
|
|
68
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
69
|
+
logDebug('Event sent successfully');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
logDebug(`Failed to send event. Status: ${res.statusCode}`);
|
|
73
|
+
handleRetry();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
const handleRetry = () => {
|
|
78
|
+
if (retriesRemaining > 0) {
|
|
79
|
+
logDebug(`Retry attempt ${2 - retriesRemaining}`);
|
|
80
|
+
sendPayload(apiUrl, apiKey, payload, debug, retriesRemaining - 1);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
req.on('error', (err) => {
|
|
84
|
+
logDebug(`Request error: ${err.message}`);
|
|
85
|
+
handleRetry();
|
|
86
|
+
});
|
|
87
|
+
req.on('timeout', () => {
|
|
88
|
+
logDebug('Timeout reached, aborting request');
|
|
89
|
+
req.destroy();
|
|
90
|
+
});
|
|
91
|
+
req.write(postData);
|
|
92
|
+
req.end();
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
logDebug(`Transport execution failed: ${err.message}`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface VantaTraceOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
serviceName: string;
|
|
4
|
+
environment?: string;
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
apiUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface VantaTraceContext {
|
|
9
|
+
userId?: string;
|
|
10
|
+
route?: string;
|
|
11
|
+
method?: string;
|
|
12
|
+
ip?: string;
|
|
13
|
+
headers?: Record<string, any>;
|
|
14
|
+
metadata?: Record<string, any>;
|
|
15
|
+
severity?: 'critical' | 'warning' | 'info';
|
|
16
|
+
}
|
|
17
|
+
export interface ErrorPayload {
|
|
18
|
+
apiKey: string;
|
|
19
|
+
serviceName: string;
|
|
20
|
+
environment: string;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
error: {
|
|
23
|
+
message: string;
|
|
24
|
+
stack: string;
|
|
25
|
+
name: string;
|
|
26
|
+
fingerprint: string;
|
|
27
|
+
code?: string;
|
|
28
|
+
statusCode?: number;
|
|
29
|
+
extra?: Record<string, any>;
|
|
30
|
+
};
|
|
31
|
+
context: VantaTraceContext;
|
|
32
|
+
system: {
|
|
33
|
+
nodeVersion: string;
|
|
34
|
+
hostname: string;
|
|
35
|
+
pid: number;
|
|
36
|
+
platform: string;
|
|
37
|
+
arch: string;
|
|
38
|
+
memory: {
|
|
39
|
+
rss: number;
|
|
40
|
+
heapTotal: number;
|
|
41
|
+
heapUsed: number;
|
|
42
|
+
external?: number;
|
|
43
|
+
freeMem: number;
|
|
44
|
+
totalMem: number;
|
|
45
|
+
};
|
|
46
|
+
loadavg: number[];
|
|
47
|
+
uptime: number;
|
|
48
|
+
};
|
|
49
|
+
severity?: 'critical' | 'warning' | 'info';
|
|
50
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vantatrace/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Drop-in observability SDK for Node.js microservices powering error tracking, structured logging, and real-time telemetry pipelines.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"observability",
|
|
13
|
+
"error-tracking",
|
|
14
|
+
"logging",
|
|
15
|
+
"telemetry",
|
|
16
|
+
"nodejs",
|
|
17
|
+
"microservices",
|
|
18
|
+
"monitoring",
|
|
19
|
+
"crash-reporting",
|
|
20
|
+
"exception-handling",
|
|
21
|
+
"debugging",
|
|
22
|
+
"apm",
|
|
23
|
+
"distributed-systems",
|
|
24
|
+
"vantatrace"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/Iqrarijaz/vantatrace_sdk.git"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.11.0",
|
|
34
|
+
"typescript": "^5.3.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as os from 'os';
|
|
2
|
+
|
|
3
|
+
export function getSystemContext() {
|
|
4
|
+
const memoryUsage = process.memoryUsage();
|
|
5
|
+
return {
|
|
6
|
+
nodeVersion: process.version,
|
|
7
|
+
hostname: os.hostname(),
|
|
8
|
+
pid: process.pid,
|
|
9
|
+
platform: os.platform(),
|
|
10
|
+
arch: os.arch(),
|
|
11
|
+
memory: {
|
|
12
|
+
rss: memoryUsage.rss,
|
|
13
|
+
heapTotal: memoryUsage.heapTotal,
|
|
14
|
+
heapUsed: memoryUsage.heapUsed,
|
|
15
|
+
external: memoryUsage.external,
|
|
16
|
+
freeMem: os.freemem(),
|
|
17
|
+
totalMem: os.totalmem()
|
|
18
|
+
},
|
|
19
|
+
loadavg: os.loadavg(),
|
|
20
|
+
uptime: Math.round(process.uptime())
|
|
21
|
+
};
|
|
22
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { VantaTraceOptions, VantaTraceContext, ErrorPayload } from './types';
|
|
2
|
+
import { normalizeError } from './normalizer';
|
|
3
|
+
import { getSystemContext } from './context';
|
|
4
|
+
import { sendPayload } from './transport';
|
|
5
|
+
|
|
6
|
+
export class VantaTrace {
|
|
7
|
+
private apiKey: string;
|
|
8
|
+
private serviceName: string;
|
|
9
|
+
private environment: string;
|
|
10
|
+
private debug: boolean;
|
|
11
|
+
private apiUrl: string;
|
|
12
|
+
|
|
13
|
+
constructor(options: VantaTraceOptions) {
|
|
14
|
+
this.apiKey = options.apiKey || '';
|
|
15
|
+
this.serviceName = options.serviceName || 'unknown-service';
|
|
16
|
+
|
|
17
|
+
let envInput = options.environment || process.env.NODE_ENV || 'development';
|
|
18
|
+
if (this.apiKey.includes('live')) {
|
|
19
|
+
envInput = 'live';
|
|
20
|
+
} else if (this.apiKey.includes('test')) {
|
|
21
|
+
envInput = 'test';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.environment = (envInput === 'production' || envInput === 'prod' || envInput === 'live') ? 'live' : 'test';
|
|
25
|
+
this.debug = !!options.debug;
|
|
26
|
+
|
|
27
|
+
// Default to localhost:6000/api/events (Standard Ingestion Endpoint)
|
|
28
|
+
this.apiUrl = options.apiUrl || 'https://api.vantatrace.com/api/events';
|
|
29
|
+
|
|
30
|
+
if (!options.apiKey && this.debug) {
|
|
31
|
+
console.warn('[VantaTrace] WARNING: API key is missing. SDK will run in dry-run mode.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (this.debug) {
|
|
35
|
+
console.log(`[VantaTrace] Initialized SDK for service "${this.serviceName}" on environment "${this.environment}".`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Primary method to capture exceptions and send them to VantaTrace
|
|
41
|
+
*/
|
|
42
|
+
public captureException(error: any, context?: VantaTraceContext): void {
|
|
43
|
+
try {
|
|
44
|
+
if (!this.apiKey) {
|
|
45
|
+
if (this.debug) {
|
|
46
|
+
console.log('[VantaTrace] Dry-run: captured exception:', error, 'Context:', context);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const normalized = normalizeError(error);
|
|
52
|
+
const systemContext = getSystemContext();
|
|
53
|
+
|
|
54
|
+
const payload: ErrorPayload = {
|
|
55
|
+
apiKey: this.apiKey,
|
|
56
|
+
serviceName: this.serviceName,
|
|
57
|
+
environment: this.environment,
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
error: normalized,
|
|
60
|
+
context: context || {},
|
|
61
|
+
system: systemContext,
|
|
62
|
+
severity: context?.severity
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (this.debug) {
|
|
66
|
+
console.log(`[VantaTrace] Sending error event: ${normalized.name} - ${normalized.message} (Severity: ${payload.severity || 'default'})`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
sendPayload(this.apiUrl, this.apiKey, payload, this.debug);
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
if (this.debug) {
|
|
72
|
+
console.error(`[VantaTrace] Failed to capture exception internally: ${e.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Helper method to capture critical errors
|
|
79
|
+
*/
|
|
80
|
+
public captureCritical(error: any, context?: Omit<VantaTraceContext, 'severity'>): void {
|
|
81
|
+
this.captureException(error, { ...context, severity: 'critical' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Helper method to capture warning errors
|
|
86
|
+
*/
|
|
87
|
+
public captureWarning(error: any, context?: Omit<VantaTraceContext, 'severity'>): void {
|
|
88
|
+
this.captureException(error, { ...context, severity: 'warning' });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Helper method to capture info messages/errors
|
|
93
|
+
*/
|
|
94
|
+
public captureInfo(error: any, context?: Omit<VantaTraceContext, 'severity'>): void {
|
|
95
|
+
this.captureException(error, { ...context, severity: 'info' });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Express error handling middleware
|
|
100
|
+
*/
|
|
101
|
+
public expressMiddleware() {
|
|
102
|
+
return (err: any, req: any, res: any, next: any) => {
|
|
103
|
+
let userId = undefined;
|
|
104
|
+
if (req.user && typeof req.user === 'object') {
|
|
105
|
+
userId = req.user.id || req.user._id || req.user.userId;
|
|
106
|
+
} else if (req.userId) {
|
|
107
|
+
userId = req.userId;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Sanitize request body if password exists
|
|
111
|
+
let sanitizedBody = undefined;
|
|
112
|
+
if (req.body && typeof req.body === 'object') {
|
|
113
|
+
sanitizedBody = { ...req.body };
|
|
114
|
+
if ('password' in sanitizedBody) sanitizedBody.password = '[REDACTED]';
|
|
115
|
+
if ('token' in sanitizedBody) sanitizedBody.token = '[REDACTED]';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Extract client IP
|
|
119
|
+
const ip = req.ip || (req.headers['x-forwarded-for'] as string) || req.socket?.remoteAddress;
|
|
120
|
+
|
|
121
|
+
// Extract and sanitize headers
|
|
122
|
+
const sanitizedHeaders: Record<string, any> = {};
|
|
123
|
+
const sensitiveHeaderKeys = ['authorization', 'cookie', 'set-cookie', 'x-api-key', 'proxy-authorization'];
|
|
124
|
+
if (req.headers && typeof req.headers === 'object') {
|
|
125
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
126
|
+
if (sensitiveHeaderKeys.includes(key.toLowerCase())) {
|
|
127
|
+
sanitizedHeaders[key] = '[REDACTED]';
|
|
128
|
+
} else {
|
|
129
|
+
sanitizedHeaders[key] = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.captureException(err, {
|
|
135
|
+
userId: userId ? String(userId) : undefined,
|
|
136
|
+
route: req.route?.path || req.path || req.url,
|
|
137
|
+
method: req.method,
|
|
138
|
+
ip: ip ? String(ip) : undefined,
|
|
139
|
+
headers: sanitizedHeaders,
|
|
140
|
+
metadata: {
|
|
141
|
+
query: req.query,
|
|
142
|
+
body: sanitizedBody
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
next(err);
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Automatically catch all uncaught exceptions and unhandled rejections
|
|
151
|
+
*/
|
|
152
|
+
public initGlobalHandlers(): void {
|
|
153
|
+
process.on('uncaughtException', (err) => {
|
|
154
|
+
if (this.debug) {
|
|
155
|
+
console.log('[VantaTrace] Captured uncaught exception globally');
|
|
156
|
+
}
|
|
157
|
+
this.captureException(err);
|
|
158
|
+
|
|
159
|
+
// Let the exception bubble up to avoid inconsistent process state (standard practice)
|
|
160
|
+
// but give a short time window for the async request to finish sending.
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}, 500);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
process.on('unhandledRejection', (reason) => {
|
|
167
|
+
if (this.debug) {
|
|
168
|
+
console.log('[VantaTrace] Captured unhandled promise rejection globally');
|
|
169
|
+
}
|
|
170
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
171
|
+
this.captureException(err);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export default VantaTrace;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
export function getFingerprint(name: string, message: string, stack: string): string {
|
|
4
|
+
const hash = crypto.createHash('md5');
|
|
5
|
+
// Group by name, message, and the first 3 lines of the stack trace to ignore line number variations deep in libraries
|
|
6
|
+
const cleanStack = (stack || '')
|
|
7
|
+
.split('\n')
|
|
8
|
+
.slice(0, 4)
|
|
9
|
+
.map(line => line.trim())
|
|
10
|
+
.join('\n');
|
|
11
|
+
hash.update(`${name}:${message}:${cleanStack}`);
|
|
12
|
+
return hash.digest('hex');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizeError(err: any): {
|
|
16
|
+
name: string;
|
|
17
|
+
message: string;
|
|
18
|
+
stack: string;
|
|
19
|
+
fingerprint: string;
|
|
20
|
+
code?: string;
|
|
21
|
+
statusCode?: number;
|
|
22
|
+
extra?: Record<string, any>;
|
|
23
|
+
} {
|
|
24
|
+
let name = 'Error';
|
|
25
|
+
let message = 'Unknown error';
|
|
26
|
+
let stack = '';
|
|
27
|
+
let code: string | undefined = undefined;
|
|
28
|
+
let statusCode: number | undefined = undefined;
|
|
29
|
+
let extra: Record<string, any> | undefined = undefined;
|
|
30
|
+
|
|
31
|
+
if (err instanceof Error) {
|
|
32
|
+
name = err.name || 'Error';
|
|
33
|
+
message = err.message || 'Unknown error';
|
|
34
|
+
stack = err.stack || '';
|
|
35
|
+
|
|
36
|
+
if ('code' in err) code = String((err as any).code);
|
|
37
|
+
if ('status' in err) statusCode = Number((err as any).status);
|
|
38
|
+
if ('statusCode' in err) statusCode = Number((err as any).statusCode);
|
|
39
|
+
|
|
40
|
+
const extraKeys = Object.keys(err).filter(k => !['name', 'message', 'stack', 'code', 'status', 'statusCode'].includes(k));
|
|
41
|
+
if (extraKeys.length > 0) {
|
|
42
|
+
extra = {};
|
|
43
|
+
for (const key of extraKeys) {
|
|
44
|
+
extra[key] = (err as any)[key];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else if (typeof err === 'string') {
|
|
48
|
+
message = err;
|
|
49
|
+
stack = new Error(err).stack || '';
|
|
50
|
+
} else if (err && typeof err === 'object') {
|
|
51
|
+
name = err.name || err.constructor?.name || 'Error';
|
|
52
|
+
message = err.message || JSON.stringify(err);
|
|
53
|
+
stack = err.stack || new Error(message).stack || '';
|
|
54
|
+
|
|
55
|
+
if ('code' in err) code = String(err.code);
|
|
56
|
+
if ('status' in err) statusCode = Number(err.status);
|
|
57
|
+
if ('statusCode' in err) statusCode = Number(err.statusCode);
|
|
58
|
+
|
|
59
|
+
const extraKeys = Object.keys(err).filter(k => !['name', 'message', 'stack', 'code', 'status', 'statusCode'].includes(k));
|
|
60
|
+
if (extraKeys.length > 0) {
|
|
61
|
+
extra = {};
|
|
62
|
+
for (const key of extraKeys) {
|
|
63
|
+
extra[key] = err[key];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
message = String(err);
|
|
68
|
+
stack = new Error(message).stack || '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const fingerprint = getFingerprint(name, message, stack);
|
|
72
|
+
|
|
73
|
+
return { name, message, stack, fingerprint, code, statusCode, extra };
|
|
74
|
+
}
|
package/src/transport.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import * as https from 'https';
|
|
3
|
+
import { URL } from 'url';
|
|
4
|
+
import { ErrorPayload } from './types';
|
|
5
|
+
|
|
6
|
+
export function sendPayload(
|
|
7
|
+
apiUrl: string,
|
|
8
|
+
apiKey: string,
|
|
9
|
+
payload: ErrorPayload,
|
|
10
|
+
debug: boolean = false,
|
|
11
|
+
retriesRemaining: number = 1
|
|
12
|
+
): void {
|
|
13
|
+
const logDebug = (msg: string) => {
|
|
14
|
+
if (debug) {
|
|
15
|
+
console.log(`[VantaTrace] ${msg}`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Run asynchronously to ensure we don't block the caller
|
|
20
|
+
process.nextTick(() => {
|
|
21
|
+
try {
|
|
22
|
+
const parsedUrl = new URL(apiUrl);
|
|
23
|
+
const postData = JSON.stringify(payload);
|
|
24
|
+
|
|
25
|
+
const options: http.RequestOptions | https.RequestOptions = {
|
|
26
|
+
hostname: parsedUrl.hostname,
|
|
27
|
+
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
|
28
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
33
|
+
'x-api-key': apiKey
|
|
34
|
+
},
|
|
35
|
+
timeout: 300 // 300ms max timeout
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const client = parsedUrl.protocol === 'https:' ? https : http;
|
|
39
|
+
|
|
40
|
+
const req = client.request(options, (res) => {
|
|
41
|
+
// Consume response data to prevent resource leaks
|
|
42
|
+
res.on('data', () => {});
|
|
43
|
+
res.on('end', () => {
|
|
44
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
45
|
+
logDebug('Event sent successfully');
|
|
46
|
+
} else {
|
|
47
|
+
logDebug(`Failed to send event. Status: ${res.statusCode}`);
|
|
48
|
+
handleRetry();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const handleRetry = () => {
|
|
54
|
+
if (retriesRemaining > 0) {
|
|
55
|
+
logDebug(`Retry attempt ${2 - retriesRemaining}`);
|
|
56
|
+
sendPayload(apiUrl, apiKey, payload, debug, retriesRemaining - 1);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
req.on('error', (err) => {
|
|
61
|
+
logDebug(`Request error: ${err.message}`);
|
|
62
|
+
handleRetry();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
req.on('timeout', () => {
|
|
66
|
+
logDebug('Timeout reached, aborting request');
|
|
67
|
+
req.destroy();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
req.write(postData);
|
|
71
|
+
req.end();
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
logDebug(`Transport execution failed: ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface VantaTraceOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
serviceName: string;
|
|
4
|
+
environment?: string;
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
apiUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface VantaTraceContext {
|
|
10
|
+
userId?: string;
|
|
11
|
+
route?: string;
|
|
12
|
+
method?: string;
|
|
13
|
+
ip?: string;
|
|
14
|
+
headers?: Record<string, any>;
|
|
15
|
+
metadata?: Record<string, any>;
|
|
16
|
+
severity?: 'critical' | 'warning' | 'info';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ErrorPayload {
|
|
20
|
+
apiKey: string;
|
|
21
|
+
serviceName: string;
|
|
22
|
+
environment: string;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
error: {
|
|
25
|
+
message: string;
|
|
26
|
+
stack: string;
|
|
27
|
+
name: string;
|
|
28
|
+
fingerprint: string;
|
|
29
|
+
code?: string;
|
|
30
|
+
statusCode?: number;
|
|
31
|
+
extra?: Record<string, any>;
|
|
32
|
+
};
|
|
33
|
+
context: VantaTraceContext;
|
|
34
|
+
system: {
|
|
35
|
+
nodeVersion: string;
|
|
36
|
+
hostname: string;
|
|
37
|
+
pid: number;
|
|
38
|
+
platform: string;
|
|
39
|
+
arch: string;
|
|
40
|
+
memory: {
|
|
41
|
+
rss: number;
|
|
42
|
+
heapTotal: number;
|
|
43
|
+
heapUsed: number;
|
|
44
|
+
external?: number;
|
|
45
|
+
freeMem: number;
|
|
46
|
+
totalMem: number;
|
|
47
|
+
};
|
|
48
|
+
loadavg: number[];
|
|
49
|
+
uptime: number;
|
|
50
|
+
};
|
|
51
|
+
severity?: 'critical' | 'warning' | 'info';
|
|
52
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["es2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|
|
Binary file
|