@gratheon/log-lib 2.1.0 → 2.2.5
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 +32 -13
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +147 -34
- package/migrations/001-create-logs-table.sql +6 -5
- package/migrations/002-add-stacktrace-column.sql +2 -0
- package/package.json +3 -2
- package/src/logger.ts +166 -34
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@ A TypeScript logging library with console output and MySQL database persistence
|
|
|
6
6
|
|
|
7
7
|
- **Dual Output**: Logs to both console and MySQL database
|
|
8
8
|
- **Color-Coded Console**: ANSI colored output for different log levels
|
|
9
|
+
- **Automatic Stacktrace Capture**: Every log includes file:line information
|
|
10
|
+
- **CLI Output**: Shows the most relevant TypeScript file:line in gray color
|
|
11
|
+
- **Database Storage**: Stores full stacktrace filtered to TypeScript files only
|
|
9
12
|
- **Enhanced Stack Traces**: Shows code frames with 5 lines of context around errors (dev mode)
|
|
10
13
|
- **Error Cause Chain Tracking**: Traverses and displays the full error.cause chain
|
|
11
14
|
- **Callsite Capture**: Captures where logger.error was called when error lacks stack frames
|
|
@@ -17,6 +20,7 @@ A TypeScript logging library with console output and MySQL database persistence
|
|
|
17
20
|
- **Fastify Integration**: Special logger interface for Fastify framework
|
|
18
21
|
- **Flexible Metadata**: Support for structured metadata in logs
|
|
19
22
|
- **Multiple Log Levels**: info, error, warn, debug (debug logs are not persisted to DB)
|
|
23
|
+
- **Log Level Filtering**: Configure minimum log level via config or LOG_LEVEL env var
|
|
20
24
|
|
|
21
25
|
## Installation
|
|
22
26
|
|
|
@@ -28,7 +32,12 @@ npm install @gratheon/log-lib
|
|
|
28
32
|
|
|
29
33
|
The logger automatically creates the database and table on first initialization. No manual setup required!
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
**Automatic Migrations**: When updating from older versions, the logger automatically runs migrations on initialization:
|
|
36
|
+
- Checks if the `stacktrace` column exists
|
|
37
|
+
- Adds it if missing (for v2.2.0+ compatibility)
|
|
38
|
+
- Non-blocking: app starts even if migration fails
|
|
39
|
+
|
|
40
|
+
For reference, migration scripts are available in the `migrations/` directory.
|
|
32
41
|
|
|
33
42
|
## Usage
|
|
34
43
|
|
|
@@ -44,14 +53,19 @@ const config: LoggerConfig = {
|
|
|
44
53
|
user: 'your_user',
|
|
45
54
|
password: 'your_password',
|
|
46
55
|
database: 'logs' // optional, defaults to 'logs'
|
|
47
|
-
}
|
|
56
|
+
},
|
|
57
|
+
logLevel: 'info' // optional, defaults to 'debug' in dev, 'info' in prod
|
|
48
58
|
};
|
|
49
59
|
|
|
50
60
|
const { logger, fastifyLogger } = createLogger(config);
|
|
51
61
|
|
|
52
|
-
// Log messages
|
|
62
|
+
// Log messages - each log automatically includes file:line location
|
|
53
63
|
logger.info('Application started');
|
|
64
|
+
// Output: 12:34:56 [info]: Application started src/index.ts:42
|
|
65
|
+
|
|
54
66
|
logger.warn('Low memory warning', { available: '100MB' });
|
|
67
|
+
// Output: 12:34:56 [warn]: Low memory warning {"available":"100MB"} src/memory.ts:15
|
|
68
|
+
|
|
55
69
|
logger.error('Failed to connect to API', { endpoint: '/api/users' });
|
|
56
70
|
logger.debug('Processing item', { id: 123 }); // Not stored in DB
|
|
57
71
|
|
|
@@ -197,23 +211,27 @@ Set `ENV_ID` to control behavior:
|
|
|
197
211
|
## Console Output Colors
|
|
198
212
|
|
|
199
213
|
- **Time**: Blue
|
|
200
|
-
- **Error**: Red (level) + Magenta (metadata)
|
|
201
|
-
- **Info**: Green (level) + Magenta (metadata)
|
|
202
|
-
- **Debug**: Gray (dimmed)
|
|
203
|
-
- **Warn**: Yellow (level) + Magenta (metadata)
|
|
214
|
+
- **Error**: Red (level) + Magenta (metadata) + Gray (file:line)
|
|
215
|
+
- **Info**: Green (level) + Magenta (metadata) + Gray (file:line)
|
|
216
|
+
- **Debug**: Gray (dimmed, including file:line)
|
|
217
|
+
- **Warn**: Yellow (level) + Magenta (metadata) + Gray (file:line)
|
|
218
|
+
- **File Location**: Gray (file:line) - automatically captured from call stack
|
|
204
219
|
|
|
205
220
|
## Database Schema
|
|
206
221
|
|
|
207
222
|
```sql
|
|
208
223
|
CREATE TABLE `logs` (
|
|
209
|
-
`id`
|
|
210
|
-
`level`
|
|
211
|
-
`message`
|
|
212
|
-
`meta`
|
|
213
|
-
`
|
|
224
|
+
`id` int auto_increment primary key,
|
|
225
|
+
`level` varchar(16) not null,
|
|
226
|
+
`message` varchar(2048) not null,
|
|
227
|
+
`meta` varchar(2048) not null,
|
|
228
|
+
`stacktrace` text,
|
|
229
|
+
`timestamp` datetime not null
|
|
214
230
|
);
|
|
215
231
|
```
|
|
216
232
|
|
|
233
|
+
The `stacktrace` column stores the full call stack filtered to TypeScript files only, making it easy to trace the origin of each log entry.
|
|
234
|
+
|
|
217
235
|
## Error Handling
|
|
218
236
|
|
|
219
237
|
The logger provides comprehensive error handling:
|
|
@@ -258,13 +276,14 @@ try {
|
|
|
258
276
|
|
|
259
277
|
```typescript
|
|
260
278
|
interface LoggerConfig {
|
|
261
|
-
mysql
|
|
279
|
+
mysql?: {
|
|
262
280
|
host: string;
|
|
263
281
|
port: number;
|
|
264
282
|
user: string;
|
|
265
283
|
password: string;
|
|
266
284
|
database?: string; // defaults to 'logs'
|
|
267
285
|
};
|
|
286
|
+
logLevel?: LogLevel; // 'debug' | 'info' | 'warn' | 'error', defaults to 'debug' in dev, 'info' in prod
|
|
268
287
|
}
|
|
269
288
|
|
|
270
289
|
interface LogMetadata {
|
package/dist/logger.d.ts
CHANGED
package/dist/logger.js
CHANGED
|
@@ -27,6 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
exports.createLogger = void 0;
|
|
30
|
+
require("source-map-support/register");
|
|
30
31
|
const mysql_1 = __importStar(require("@databases/mysql"));
|
|
31
32
|
const fast_safe_stringify_1 = __importDefault(require("fast-safe-stringify"));
|
|
32
33
|
const fs = __importStar(require("fs"));
|
|
@@ -40,6 +41,30 @@ const LOG_LEVELS = {
|
|
|
40
41
|
error: 3
|
|
41
42
|
};
|
|
42
43
|
let currentLogLevel = LOG_LEVELS.info;
|
|
44
|
+
// Get the project root (where the service is running from)
|
|
45
|
+
const projectRoot = process.cwd();
|
|
46
|
+
// Helper function to convert absolute paths to relative paths
|
|
47
|
+
function makePathRelative(filePath) {
|
|
48
|
+
if (filePath.startsWith(projectRoot)) {
|
|
49
|
+
return path.relative(projectRoot, filePath);
|
|
50
|
+
}
|
|
51
|
+
return filePath;
|
|
52
|
+
}
|
|
53
|
+
// Helper function to clean up stack trace paths
|
|
54
|
+
function cleanStackTrace(stack) {
|
|
55
|
+
if (!stack)
|
|
56
|
+
return '';
|
|
57
|
+
return stack.split('\n').map(line => {
|
|
58
|
+
// Match file paths in stack traces
|
|
59
|
+
return line.replace(/\(([^)]+)\)/g, (match, filePath) => {
|
|
60
|
+
const cleaned = makePathRelative(filePath);
|
|
61
|
+
return `(${cleaned})`;
|
|
62
|
+
}).replace(/at\s+([^\s]+:\d+:\d+)/g, (match, filePath) => {
|
|
63
|
+
const cleaned = makePathRelative(filePath);
|
|
64
|
+
return `at ${cleaned}`;
|
|
65
|
+
});
|
|
66
|
+
}).join('\n');
|
|
67
|
+
}
|
|
43
68
|
async function initializeConnection(config) {
|
|
44
69
|
if (dbInitialized || !config.mysql)
|
|
45
70
|
return;
|
|
@@ -76,11 +101,25 @@ async function initializeConnection(config) {
|
|
|
76
101
|
level VARCHAR(50),
|
|
77
102
|
message TEXT,
|
|
78
103
|
meta TEXT,
|
|
104
|
+
stacktrace TEXT,
|
|
79
105
|
timestamp DATETIME,
|
|
80
106
|
INDEX idx_timestamp (timestamp),
|
|
81
107
|
INDEX idx_level (level)
|
|
82
108
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
83
109
|
`);
|
|
110
|
+
// Run migrations: Add stacktrace column if it doesn't exist (for existing tables)
|
|
111
|
+
try {
|
|
112
|
+
const columns = await conn.query((0, mysql_1.sql) `SHOW COLUMNS FROM \`logs\` LIKE 'stacktrace'`);
|
|
113
|
+
if (columns.length === 0) {
|
|
114
|
+
console.log('[log-lib] Running migration: Adding stacktrace column...');
|
|
115
|
+
await conn.query((0, mysql_1.sql) `ALTER TABLE \`logs\` ADD COLUMN \`stacktrace\` TEXT AFTER \`meta\``);
|
|
116
|
+
console.log('[log-lib] Migration complete: stacktrace column added');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (migrationErr) {
|
|
120
|
+
console.error('[log-lib] Migration failed (non-critical):', migrationErr);
|
|
121
|
+
// Don't fail initialization if migration fails
|
|
122
|
+
}
|
|
84
123
|
dbInitialized = true;
|
|
85
124
|
}
|
|
86
125
|
catch (err) {
|
|
@@ -88,7 +127,7 @@ async function initializeConnection(config) {
|
|
|
88
127
|
// Don't throw - allow the service to start even if logging DB fails
|
|
89
128
|
}
|
|
90
129
|
}
|
|
91
|
-
function log(level, message, meta) {
|
|
130
|
+
function log(level, message, meta, fileLocation) {
|
|
92
131
|
// Check if this log level should be filtered
|
|
93
132
|
const levelKey = level.replace(/\x1b\[\d+m/g, ''); // Remove ANSI codes for comparison
|
|
94
133
|
const messageLevel = LOG_LEVELS[levelKey];
|
|
@@ -118,30 +157,36 @@ function log(level, message, meta) {
|
|
|
118
157
|
level = `\x1b[33m${level}\x1b[0m`;
|
|
119
158
|
meta = `\x1b[35m${meta}\x1b[0m`;
|
|
120
159
|
}
|
|
121
|
-
|
|
160
|
+
// Add gray file:line location if provided
|
|
161
|
+
const location = fileLocation ? ` \x1b[90m${fileLocation}\x1b[0m` : '';
|
|
162
|
+
console.log(`${hhMMTime} [${level}]: ${message} ${meta}${location}`);
|
|
122
163
|
}
|
|
123
|
-
function formatStack(stack) {
|
|
164
|
+
function formatStack(stack, maxLines = 3) {
|
|
124
165
|
if (!stack)
|
|
125
166
|
return '';
|
|
167
|
+
// Clean up paths first
|
|
168
|
+
const cleanedStack = cleanStackTrace(stack);
|
|
126
169
|
// Remove first line if it duplicates the error message already printed.
|
|
127
|
-
const lines =
|
|
170
|
+
const lines = cleanedStack.split('\n');
|
|
128
171
|
if (lines.length > 1 && lines[0].startsWith('Error')) {
|
|
129
172
|
lines.shift();
|
|
130
173
|
}
|
|
131
|
-
//
|
|
132
|
-
|
|
174
|
+
// Limit to first N lines and grey color for stack lines
|
|
175
|
+
const limitedLines = lines.slice(0, maxLines);
|
|
176
|
+
return limitedLines.map(l => `\x1b[90m${l}\x1b[0m`).join('\n');
|
|
133
177
|
}
|
|
134
178
|
function extractFirstProjectFrame(stack) {
|
|
135
179
|
if (!stack)
|
|
136
180
|
return {};
|
|
137
|
-
const
|
|
181
|
+
const cleanedStack = cleanStackTrace(stack);
|
|
182
|
+
const lines = cleanedStack.split('\n');
|
|
138
183
|
for (const l of lines) {
|
|
139
|
-
// Match: at FunctionName (
|
|
184
|
+
// Match: at FunctionName (src/some/file.ts:123:45)
|
|
140
185
|
const m = l.match(/\(([^()]+\.ts):(\d+):(\d+)\)/);
|
|
141
186
|
if (m) {
|
|
142
187
|
return { file: m[1], line: parseInt(m[2], 10), column: parseInt(m[3], 10) };
|
|
143
188
|
}
|
|
144
|
-
// Alternate format: at
|
|
189
|
+
// Alternate format: at src/file.ts:123:45
|
|
145
190
|
const m2 = l.match(/\s(at\s)?([^()]+\.ts):(\d+):(\d+)/);
|
|
146
191
|
if (m2) {
|
|
147
192
|
return { file: m2[2], line: parseInt(m2[3], 10), column: parseInt(m2[4], 10) };
|
|
@@ -149,6 +194,26 @@ function extractFirstProjectFrame(stack) {
|
|
|
149
194
|
}
|
|
150
195
|
return {};
|
|
151
196
|
}
|
|
197
|
+
function extractFullTsStacktrace(stack) {
|
|
198
|
+
if (!stack)
|
|
199
|
+
return '';
|
|
200
|
+
const cleanedStack = cleanStackTrace(stack);
|
|
201
|
+
const lines = cleanedStack.split('\n');
|
|
202
|
+
// Filter only TypeScript files
|
|
203
|
+
const tsLines = lines.filter(l => l.includes('.ts:') || l.includes('.ts)'));
|
|
204
|
+
return tsLines.join('\n');
|
|
205
|
+
}
|
|
206
|
+
function captureCallStack() {
|
|
207
|
+
const err = new Error();
|
|
208
|
+
if (!err.stack)
|
|
209
|
+
return '';
|
|
210
|
+
const cleanedStack = cleanStackTrace(err.stack);
|
|
211
|
+
const lines = cleanedStack.split('\n');
|
|
212
|
+
// Skip first line (Error:) and this function call + log function calls
|
|
213
|
+
// Keep only .ts files
|
|
214
|
+
const tsLines = lines.slice(1).filter(l => l.includes('.ts:') || l.includes('.ts)'));
|
|
215
|
+
return tsLines.join('\n');
|
|
216
|
+
}
|
|
152
217
|
function buildCodeFrame(frame) {
|
|
153
218
|
if (!frame.file || frame.line == null)
|
|
154
219
|
return '';
|
|
@@ -246,7 +311,7 @@ function safeMeta(meta) {
|
|
|
246
311
|
return {};
|
|
247
312
|
return meta;
|
|
248
313
|
}
|
|
249
|
-
function storeInDB(level, message, meta) {
|
|
314
|
+
function storeInDB(level, message, meta, stacktrace) {
|
|
250
315
|
if (!conn || !dbInitialized) {
|
|
251
316
|
// Database not ready yet, skip DB logging
|
|
252
317
|
return;
|
|
@@ -255,8 +320,9 @@ function storeInDB(level, message, meta) {
|
|
|
255
320
|
const msg = safeToStringMessage(message);
|
|
256
321
|
const metaObj = safeMeta(meta);
|
|
257
322
|
const metaStr = (0, fast_safe_stringify_1.default)(metaObj).slice(0, 2000);
|
|
323
|
+
const stackStr = stacktrace || '';
|
|
258
324
|
// Fire and forget; avoid awaiting in hot path. Catch errors to avoid unhandled rejection.
|
|
259
|
-
conn.query((0, mysql_1.sql) `INSERT INTO \`logs\` (level, message, meta, timestamp) VALUES (${level}, ${msg}, ${metaStr}, NOW())`).catch(e => {
|
|
325
|
+
conn.query((0, mysql_1.sql) `INSERT INTO \`logs\` (level, message, meta, stacktrace, timestamp) VALUES (${level}, ${msg}, ${metaStr}, ${stackStr}, NOW())`).catch(e => {
|
|
260
326
|
// fallback console output only - but don't spam
|
|
261
327
|
if (process.env.ENV_ID === 'dev') {
|
|
262
328
|
console.error('Failed to persist log to DB', e);
|
|
@@ -283,86 +349,133 @@ function createLogger(config = {}) {
|
|
|
283
349
|
const logger = {
|
|
284
350
|
info: (message, meta) => {
|
|
285
351
|
const metaObj = safeMeta(meta);
|
|
286
|
-
|
|
287
|
-
|
|
352
|
+
const callStack = captureCallStack();
|
|
353
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
354
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
355
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
356
|
+
log('info', safeToStringMessage(message), metaObj, fileLocation);
|
|
357
|
+
storeInDB('info', message, metaObj, fullTsStack);
|
|
288
358
|
},
|
|
289
359
|
error: (message, meta) => {
|
|
290
360
|
const metaObj = safeMeta(meta);
|
|
291
361
|
if (message instanceof Error) {
|
|
292
362
|
const causeChain = buildCauseChain(message);
|
|
293
|
-
const
|
|
294
|
-
|
|
363
|
+
const fullTsStack = extractFullTsStacktrace(message.stack);
|
|
364
|
+
const frame = extractFirstProjectFrame(message.stack);
|
|
365
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
366
|
+
// For console: show message + metadata (without stack), then stack separately
|
|
367
|
+
log('error', message.message, metaObj, fileLocation);
|
|
295
368
|
if (message.stack) {
|
|
296
369
|
printStackEnhanced(message);
|
|
297
370
|
}
|
|
298
371
|
if (causeChain.length) {
|
|
299
372
|
console.log('\x1b[35mCause chain:\x1b[0m ' + causeChain.join(' -> '));
|
|
300
373
|
}
|
|
301
|
-
|
|
374
|
+
// For DB: include stack and error details in metadata
|
|
375
|
+
const enrichedMeta = { stack: message.stack, name: message.name, causeChain, ...metaObj };
|
|
376
|
+
storeInDB('error', message.message, enrichedMeta, fullTsStack);
|
|
302
377
|
return;
|
|
303
378
|
}
|
|
304
379
|
const msgStr = safeToStringMessage(message);
|
|
305
|
-
|
|
380
|
+
const callStack = captureCallStack();
|
|
381
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
382
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
383
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
384
|
+
log('error', msgStr, metaObj, fileLocation);
|
|
306
385
|
printStackEnhanced(message);
|
|
307
|
-
storeInDB('error', msgStr, metaObj);
|
|
386
|
+
storeInDB('error', msgStr, metaObj, fullTsStack);
|
|
308
387
|
},
|
|
309
388
|
errorEnriched: (message, error, meta) => {
|
|
310
389
|
const metaObj = safeMeta(meta);
|
|
311
390
|
if (error instanceof Error) {
|
|
312
391
|
const causeChain = buildCauseChain(error);
|
|
313
|
-
const
|
|
314
|
-
|
|
392
|
+
const fullTsStack = extractFullTsStacktrace(error.stack);
|
|
393
|
+
const frame = extractFirstProjectFrame(error.stack);
|
|
394
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
395
|
+
// For console: show message + metadata (without stack), then stack separately
|
|
396
|
+
log('error', `${message}: ${error.message}`, metaObj, fileLocation);
|
|
315
397
|
if (error.stack) {
|
|
316
398
|
printStackEnhanced(error);
|
|
317
399
|
}
|
|
318
400
|
if (causeChain.length) {
|
|
319
401
|
console.log('\x1b[35mCause chain:\x1b[0m ' + causeChain.join(' -> '));
|
|
320
402
|
}
|
|
321
|
-
|
|
403
|
+
// For DB: include stack and error details in metadata
|
|
404
|
+
const enrichedMeta = { stack: error.stack, name: error.name, causeChain, ...metaObj };
|
|
405
|
+
storeInDB('error', `${message}: ${error.message}`, enrichedMeta, fullTsStack);
|
|
322
406
|
return;
|
|
323
407
|
}
|
|
324
408
|
const errStr = safeToStringMessage(error);
|
|
325
|
-
|
|
409
|
+
const callStack = captureCallStack();
|
|
410
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
411
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
412
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
413
|
+
log('error', `${message}: ${errStr}`, metaObj, fileLocation);
|
|
326
414
|
printStackEnhanced(error);
|
|
327
|
-
storeInDB('error', `${message}: ${errStr}`, metaObj);
|
|
415
|
+
storeInDB('error', `${message}: ${errStr}`, metaObj, fullTsStack);
|
|
328
416
|
},
|
|
329
417
|
warn: (message, meta) => {
|
|
330
418
|
const metaObj = safeMeta(meta);
|
|
331
|
-
|
|
332
|
-
|
|
419
|
+
const callStack = captureCallStack();
|
|
420
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
421
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
422
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
423
|
+
log('warn', safeToStringMessage(message), metaObj, fileLocation);
|
|
424
|
+
storeInDB('warn', message, metaObj, fullTsStack);
|
|
333
425
|
},
|
|
334
426
|
// do not store debug logs in DB
|
|
335
427
|
debug: (message, meta) => {
|
|
336
|
-
|
|
428
|
+
const callStack = captureCallStack();
|
|
429
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
430
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
431
|
+
log('debug', safeToStringMessage(message), safeMeta(meta), fileLocation);
|
|
337
432
|
},
|
|
338
433
|
};
|
|
339
434
|
const fastifyLogger = {
|
|
340
435
|
// Stringify potential objects passed to info/warn
|
|
341
436
|
info: (msg, ...args) => {
|
|
342
437
|
const messageString = typeof msg === 'object' ? (0, fast_safe_stringify_1.default)(msg) : String(msg);
|
|
343
|
-
|
|
438
|
+
const callStack = captureCallStack();
|
|
439
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
440
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
441
|
+
log("info", messageString, undefined, fileLocation);
|
|
344
442
|
// storeInDB("info", messageString); // Keep commented out as original
|
|
345
443
|
},
|
|
346
444
|
error: (msg, ...args) => {
|
|
347
445
|
const errorMessage = (msg && msg.message) ? msg.message : String(msg);
|
|
348
446
|
const meta = args.length > 0 ? args[0] : undefined;
|
|
349
|
-
|
|
447
|
+
const callStack = msg?.stack || captureCallStack();
|
|
448
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
449
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
450
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
451
|
+
log("error", errorMessage, meta, fileLocation);
|
|
350
452
|
// Ensure string is passed to storeInDB
|
|
351
|
-
storeInDB("error", typeof msg === 'object' ? (0, fast_safe_stringify_1.default)(msg) : errorMessage, meta);
|
|
453
|
+
storeInDB("error", typeof msg === 'object' ? (0, fast_safe_stringify_1.default)(msg) : errorMessage, meta, fullTsStack);
|
|
352
454
|
},
|
|
353
455
|
warn: (msg, ...args) => {
|
|
354
456
|
const messageString = typeof msg === 'object' ? (0, fast_safe_stringify_1.default)(msg) : String(msg);
|
|
355
|
-
|
|
356
|
-
|
|
457
|
+
const callStack = captureCallStack();
|
|
458
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
459
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
460
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
461
|
+
log("warn", messageString, undefined, fileLocation);
|
|
462
|
+
storeInDB("warn", messageString, undefined, fullTsStack); // Pass stringified message
|
|
357
463
|
},
|
|
358
464
|
// do not store debug logs in DB
|
|
359
465
|
debug: (msg, ...args) => {
|
|
360
|
-
|
|
466
|
+
const callStack = captureCallStack();
|
|
467
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
468
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
469
|
+
log("debug", String(msg), undefined, fileLocation);
|
|
361
470
|
},
|
|
362
471
|
fatal: (msg, ...args) => {
|
|
363
472
|
const messageString = typeof msg === 'object' ? (0, fast_safe_stringify_1.default)(msg) : String(msg);
|
|
364
|
-
|
|
365
|
-
|
|
473
|
+
const callStack = captureCallStack();
|
|
474
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
475
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
476
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
477
|
+
log("error", messageString, undefined, fileLocation);
|
|
478
|
+
storeInDB("error", messageString, undefined, fullTsStack);
|
|
366
479
|
// Exit after a brief delay to allow logs to flush
|
|
367
480
|
setTimeout(() => process.exit(1), 100);
|
|
368
481
|
},
|
|
@@ -3,9 +3,10 @@ CREATE DATABASE IF NOT EXISTS `logs`;
|
|
|
3
3
|
|
|
4
4
|
-- Create the logs table within the 'logs' database
|
|
5
5
|
CREATE TABLE IF NOT EXISTS `logs`.`logs` (
|
|
6
|
-
`id`
|
|
7
|
-
`level`
|
|
8
|
-
`message`
|
|
9
|
-
`meta`
|
|
10
|
-
`
|
|
6
|
+
`id` int auto_increment primary key,
|
|
7
|
+
`level` varchar(16) not null,
|
|
8
|
+
`message` varchar(2048) not null,
|
|
9
|
+
`meta` varchar(2048) not null,
|
|
10
|
+
`stacktrace` text,
|
|
11
|
+
`timestamp` datetime not null
|
|
11
12
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gratheon/log-lib",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.5",
|
|
4
4
|
"description": "Logging library with console and MySQL database persistence",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@databases/mysql": "^7.0.0",
|
|
22
|
-
"fast-safe-stringify": "^2.1.1"
|
|
22
|
+
"fast-safe-stringify": "^2.1.1",
|
|
23
|
+
"source-map-support": "^0.5.21"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@types/node": "^18.11.11",
|
package/src/logger.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import 'source-map-support/register';
|
|
1
2
|
import createConnectionPool, { sql, ConnectionPool } from "@databases/mysql";
|
|
2
3
|
import jsonStringify from "fast-safe-stringify";
|
|
3
4
|
import * as fs from 'fs';
|
|
@@ -16,6 +17,33 @@ const LOG_LEVELS: Record<LogLevel, number> = {
|
|
|
16
17
|
|
|
17
18
|
let currentLogLevel: number = LOG_LEVELS.info;
|
|
18
19
|
|
|
20
|
+
// Get the project root (where the service is running from)
|
|
21
|
+
const projectRoot = process.cwd();
|
|
22
|
+
|
|
23
|
+
// Helper function to convert absolute paths to relative paths
|
|
24
|
+
function makePathRelative(filePath: string): string {
|
|
25
|
+
if (filePath.startsWith(projectRoot)) {
|
|
26
|
+
return path.relative(projectRoot, filePath);
|
|
27
|
+
}
|
|
28
|
+
return filePath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Helper function to clean up stack trace paths
|
|
32
|
+
function cleanStackTrace(stack: string): string {
|
|
33
|
+
if (!stack) return '';
|
|
34
|
+
|
|
35
|
+
return stack.split('\n').map(line => {
|
|
36
|
+
// Match file paths in stack traces
|
|
37
|
+
return line.replace(/\(([^)]+)\)/g, (match, filePath) => {
|
|
38
|
+
const cleaned = makePathRelative(filePath);
|
|
39
|
+
return `(${cleaned})`;
|
|
40
|
+
}).replace(/at\s+([^\s]+:\d+:\d+)/g, (match, filePath) => {
|
|
41
|
+
const cleaned = makePathRelative(filePath);
|
|
42
|
+
return `at ${cleaned}`;
|
|
43
|
+
});
|
|
44
|
+
}).join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
19
47
|
async function initializeConnection(config: LoggerConfig) {
|
|
20
48
|
if (dbInitialized || !config.mysql) return;
|
|
21
49
|
|
|
@@ -56,12 +84,26 @@ async function initializeConnection(config: LoggerConfig) {
|
|
|
56
84
|
level VARCHAR(50),
|
|
57
85
|
message TEXT,
|
|
58
86
|
meta TEXT,
|
|
87
|
+
stacktrace TEXT,
|
|
59
88
|
timestamp DATETIME,
|
|
60
89
|
INDEX idx_timestamp (timestamp),
|
|
61
90
|
INDEX idx_level (level)
|
|
62
91
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
63
92
|
`);
|
|
64
93
|
|
|
94
|
+
// Run migrations: Add stacktrace column if it doesn't exist (for existing tables)
|
|
95
|
+
try {
|
|
96
|
+
const columns = await conn.query(sql`SHOW COLUMNS FROM \`logs\` LIKE 'stacktrace'`);
|
|
97
|
+
if (columns.length === 0) {
|
|
98
|
+
console.log('[log-lib] Running migration: Adding stacktrace column...');
|
|
99
|
+
await conn.query(sql`ALTER TABLE \`logs\` ADD COLUMN \`stacktrace\` TEXT AFTER \`meta\``);
|
|
100
|
+
console.log('[log-lib] Migration complete: stacktrace column added');
|
|
101
|
+
}
|
|
102
|
+
} catch (migrationErr) {
|
|
103
|
+
console.error('[log-lib] Migration failed (non-critical):', migrationErr);
|
|
104
|
+
// Don't fail initialization if migration fails
|
|
105
|
+
}
|
|
106
|
+
|
|
65
107
|
dbInitialized = true;
|
|
66
108
|
} catch (err) {
|
|
67
109
|
console.error('Failed to initialize logs database:', err);
|
|
@@ -69,7 +111,7 @@ async function initializeConnection(config: LoggerConfig) {
|
|
|
69
111
|
}
|
|
70
112
|
}
|
|
71
113
|
|
|
72
|
-
function log(level: string, message: string, meta?: any) {
|
|
114
|
+
function log(level: string, message: string, meta?: any, fileLocation?: string) {
|
|
73
115
|
// Check if this log level should be filtered
|
|
74
116
|
const levelKey = level.replace(/\x1b\[\d+m/g, '') as LogLevel; // Remove ANSI codes for comparison
|
|
75
117
|
const messageLevel = LOG_LEVELS[levelKey];
|
|
@@ -100,30 +142,38 @@ function log(level: string, message: string, meta?: any) {
|
|
|
100
142
|
meta = `\x1b[35m${meta}\x1b[0m`;
|
|
101
143
|
}
|
|
102
144
|
|
|
103
|
-
|
|
145
|
+
// Add gray file:line location if provided
|
|
146
|
+
const location = fileLocation ? ` \x1b[90m${fileLocation}\x1b[0m` : '';
|
|
147
|
+
|
|
148
|
+
console.log(`${hhMMTime} [${level}]: ${message} ${meta}${location}`);
|
|
104
149
|
}
|
|
105
150
|
|
|
106
|
-
function formatStack(stack?: string): string {
|
|
151
|
+
function formatStack(stack?: string, maxLines: number = 3): string {
|
|
107
152
|
if (!stack) return '';
|
|
153
|
+
// Clean up paths first
|
|
154
|
+
const cleanedStack = cleanStackTrace(stack);
|
|
155
|
+
|
|
108
156
|
// Remove first line if it duplicates the error message already printed.
|
|
109
|
-
const lines =
|
|
157
|
+
const lines = cleanedStack.split('\n');
|
|
110
158
|
if (lines.length > 1 && lines[0].startsWith('Error')) {
|
|
111
159
|
lines.shift();
|
|
112
160
|
}
|
|
113
|
-
//
|
|
114
|
-
|
|
161
|
+
// Limit to first N lines and grey color for stack lines
|
|
162
|
+
const limitedLines = lines.slice(0, maxLines);
|
|
163
|
+
return limitedLines.map(l => `\x1b[90m${l}\x1b[0m`).join('\n');
|
|
115
164
|
}
|
|
116
165
|
|
|
117
166
|
function extractFirstProjectFrame(stack?: string): {file?: string, line?: number, column?: number} {
|
|
118
167
|
if (!stack) return {};
|
|
119
|
-
const
|
|
168
|
+
const cleanedStack = cleanStackTrace(stack);
|
|
169
|
+
const lines = cleanedStack.split('\n');
|
|
120
170
|
for (const l of lines) {
|
|
121
|
-
// Match: at FunctionName (
|
|
171
|
+
// Match: at FunctionName (src/some/file.ts:123:45)
|
|
122
172
|
const m = l.match(/\(([^()]+\.ts):(\d+):(\d+)\)/);
|
|
123
173
|
if (m) {
|
|
124
174
|
return {file: m[1], line: parseInt(m[2], 10), column: parseInt(m[3], 10)};
|
|
125
175
|
}
|
|
126
|
-
// Alternate format: at
|
|
176
|
+
// Alternate format: at src/file.ts:123:45
|
|
127
177
|
const m2 = l.match(/\s(at\s)?([^()]+\.ts):(\d+):(\d+)/);
|
|
128
178
|
if (m2) {
|
|
129
179
|
return {file: m2[2], line: parseInt(m2[3], 10), column: parseInt(m2[4], 10)};
|
|
@@ -132,6 +182,26 @@ function extractFirstProjectFrame(stack?: string): {file?: string, line?: number
|
|
|
132
182
|
return {};
|
|
133
183
|
}
|
|
134
184
|
|
|
185
|
+
function extractFullTsStacktrace(stack?: string): string {
|
|
186
|
+
if (!stack) return '';
|
|
187
|
+
const cleanedStack = cleanStackTrace(stack);
|
|
188
|
+
const lines = cleanedStack.split('\n');
|
|
189
|
+
// Filter only TypeScript files
|
|
190
|
+
const tsLines = lines.filter(l => l.includes('.ts:') || l.includes('.ts)'));
|
|
191
|
+
return tsLines.join('\n');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function captureCallStack(): string {
|
|
195
|
+
const err = new Error();
|
|
196
|
+
if (!err.stack) return '';
|
|
197
|
+
const cleanedStack = cleanStackTrace(err.stack);
|
|
198
|
+
const lines = cleanedStack.split('\n');
|
|
199
|
+
// Skip first line (Error:) and this function call + log function calls
|
|
200
|
+
// Keep only .ts files
|
|
201
|
+
const tsLines = lines.slice(1).filter(l => l.includes('.ts:') || l.includes('.ts)'));
|
|
202
|
+
return tsLines.join('\n');
|
|
203
|
+
}
|
|
204
|
+
|
|
135
205
|
function buildCodeFrame(frame: {file?: string, line?: number, column?: number}): string {
|
|
136
206
|
if (!frame.file || frame.line == null) return '';
|
|
137
207
|
try {
|
|
@@ -223,7 +293,7 @@ function safeMeta(meta: any): any {
|
|
|
223
293
|
return meta;
|
|
224
294
|
}
|
|
225
295
|
|
|
226
|
-
function storeInDB(level: string, message: any, meta?: any) {
|
|
296
|
+
function storeInDB(level: string, message: any, meta?: any, stacktrace?: string) {
|
|
227
297
|
if (!conn || !dbInitialized) {
|
|
228
298
|
// Database not ready yet, skip DB logging
|
|
229
299
|
return;
|
|
@@ -232,8 +302,9 @@ function storeInDB(level: string, message: any, meta?: any) {
|
|
|
232
302
|
const msg = safeToStringMessage(message);
|
|
233
303
|
const metaObj = safeMeta(meta);
|
|
234
304
|
const metaStr = jsonStringify(metaObj).slice(0, 2000);
|
|
305
|
+
const stackStr = stacktrace || '';
|
|
235
306
|
// Fire and forget; avoid awaiting in hot path. Catch errors to avoid unhandled rejection.
|
|
236
|
-
conn.query(sql`INSERT INTO \`logs\` (level, message, meta, timestamp) VALUES (${level}, ${msg}, ${metaStr}, NOW())`).catch(e => {
|
|
307
|
+
conn.query(sql`INSERT INTO \`logs\` (level, message, meta, stacktrace, timestamp) VALUES (${level}, ${msg}, ${metaStr}, ${stackStr}, NOW())`).catch(e => {
|
|
237
308
|
// fallback console output only - but don't spam
|
|
238
309
|
if (process.env.ENV_ID === 'dev') {
|
|
239
310
|
console.error('Failed to persist log to DB', e);
|
|
@@ -263,58 +334,96 @@ export function createLogger(config: LoggerConfig = {}): { logger: Logger; fasti
|
|
|
263
334
|
const logger: Logger = {
|
|
264
335
|
info: (message: string, meta?: LogMetadata) => {
|
|
265
336
|
const metaObj = safeMeta(meta);
|
|
266
|
-
|
|
267
|
-
|
|
337
|
+
const callStack = captureCallStack();
|
|
338
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
339
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
340
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
341
|
+
|
|
342
|
+
log('info', safeToStringMessage(message), metaObj, fileLocation);
|
|
343
|
+
storeInDB('info', message, metaObj, fullTsStack);
|
|
268
344
|
},
|
|
269
345
|
error: (message: string | Error | any, meta?: LogMetadata) => {
|
|
270
346
|
const metaObj = safeMeta(meta);
|
|
271
347
|
if (message instanceof Error) {
|
|
272
348
|
const causeChain = buildCauseChain(message);
|
|
273
|
-
const
|
|
274
|
-
|
|
349
|
+
const fullTsStack = extractFullTsStacktrace(message.stack);
|
|
350
|
+
const frame = extractFirstProjectFrame(message.stack);
|
|
351
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
352
|
+
|
|
353
|
+
// For console: show message + metadata (without stack), then stack separately
|
|
354
|
+
log('error', message.message, metaObj, fileLocation);
|
|
275
355
|
if (message.stack) {
|
|
276
356
|
printStackEnhanced(message);
|
|
277
357
|
}
|
|
278
358
|
if (causeChain.length) {
|
|
279
359
|
console.log('\x1b[35mCause chain:\x1b[0m ' + causeChain.join(' -> '));
|
|
280
360
|
}
|
|
281
|
-
|
|
361
|
+
|
|
362
|
+
// For DB: include stack and error details in metadata
|
|
363
|
+
const enrichedMeta = {stack: message.stack, name: message.name, causeChain, ...metaObj};
|
|
364
|
+
storeInDB('error', message.message, enrichedMeta, fullTsStack);
|
|
282
365
|
return;
|
|
283
366
|
}
|
|
284
367
|
const msgStr = safeToStringMessage(message);
|
|
285
|
-
|
|
368
|
+
const callStack = captureCallStack();
|
|
369
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
370
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
371
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
372
|
+
|
|
373
|
+
log('error', msgStr, metaObj, fileLocation);
|
|
286
374
|
printStackEnhanced(message);
|
|
287
|
-
storeInDB('error', msgStr, metaObj);
|
|
375
|
+
storeInDB('error', msgStr, metaObj, fullTsStack);
|
|
288
376
|
},
|
|
289
377
|
errorEnriched: (message: string, error: Error | any, meta?: LogMetadata) => {
|
|
290
378
|
const metaObj = safeMeta(meta);
|
|
291
379
|
if (error instanceof Error) {
|
|
292
380
|
const causeChain = buildCauseChain(error);
|
|
293
|
-
const
|
|
294
|
-
|
|
381
|
+
const fullTsStack = extractFullTsStacktrace(error.stack);
|
|
382
|
+
const frame = extractFirstProjectFrame(error.stack);
|
|
383
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
384
|
+
|
|
385
|
+
// For console: show message + metadata (without stack), then stack separately
|
|
386
|
+
log('error', `${message}: ${error.message}`, metaObj, fileLocation);
|
|
295
387
|
if (error.stack) {
|
|
296
388
|
printStackEnhanced(error);
|
|
297
389
|
}
|
|
298
390
|
if (causeChain.length) {
|
|
299
391
|
console.log('\x1b[35mCause chain:\x1b[0m ' + causeChain.join(' -> '));
|
|
300
392
|
}
|
|
301
|
-
|
|
393
|
+
|
|
394
|
+
// For DB: include stack and error details in metadata
|
|
395
|
+
const enrichedMeta = {stack: error.stack, name: error.name, causeChain, ...metaObj};
|
|
396
|
+
storeInDB('error', `${message}: ${error.message}`, enrichedMeta, fullTsStack);
|
|
302
397
|
return;
|
|
303
398
|
}
|
|
304
399
|
const errStr = safeToStringMessage(error);
|
|
305
|
-
|
|
400
|
+
const callStack = captureCallStack();
|
|
401
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
402
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
403
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
404
|
+
|
|
405
|
+
log('error', `${message}: ${errStr}`, metaObj, fileLocation);
|
|
306
406
|
printStackEnhanced(error);
|
|
307
|
-
storeInDB('error', `${message}: ${errStr}`, metaObj);
|
|
407
|
+
storeInDB('error', `${message}: ${errStr}`, metaObj, fullTsStack);
|
|
308
408
|
},
|
|
309
409
|
warn: (message: string, meta?: LogMetadata) => {
|
|
310
410
|
const metaObj = safeMeta(meta);
|
|
311
|
-
|
|
312
|
-
|
|
411
|
+
const callStack = captureCallStack();
|
|
412
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
413
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
414
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
415
|
+
|
|
416
|
+
log('warn', safeToStringMessage(message), metaObj, fileLocation);
|
|
417
|
+
storeInDB('warn', message, metaObj, fullTsStack);
|
|
313
418
|
},
|
|
314
419
|
|
|
315
420
|
// do not store debug logs in DB
|
|
316
421
|
debug: (message: string, meta?: LogMetadata) => {
|
|
317
|
-
|
|
422
|
+
const callStack = captureCallStack();
|
|
423
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
424
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
425
|
+
|
|
426
|
+
log('debug', safeToStringMessage(message), safeMeta(meta), fileLocation);
|
|
318
427
|
},
|
|
319
428
|
};
|
|
320
429
|
|
|
@@ -322,31 +431,54 @@ export function createLogger(config: LoggerConfig = {}): { logger: Logger; fasti
|
|
|
322
431
|
// Stringify potential objects passed to info/warn
|
|
323
432
|
info: (msg: any, ...args: any[]) => {
|
|
324
433
|
const messageString = typeof msg === 'object' ? jsonStringify(msg) : String(msg);
|
|
325
|
-
|
|
434
|
+
const callStack = captureCallStack();
|
|
435
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
436
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
437
|
+
|
|
438
|
+
log("info", messageString, undefined, fileLocation);
|
|
326
439
|
// storeInDB("info", messageString); // Keep commented out as original
|
|
327
440
|
},
|
|
328
441
|
error: (msg: any, ...args: any[]) => {
|
|
329
442
|
const errorMessage = (msg && msg.message) ? msg.message : String(msg);
|
|
330
443
|
const meta = args.length > 0 ? args[0] : undefined;
|
|
331
|
-
|
|
444
|
+
const callStack = msg?.stack || captureCallStack();
|
|
445
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
446
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
447
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
448
|
+
|
|
449
|
+
log("error", errorMessage, meta, fileLocation);
|
|
332
450
|
// Ensure string is passed to storeInDB
|
|
333
|
-
storeInDB("error", typeof msg === 'object' ? jsonStringify(msg) : errorMessage, meta);
|
|
451
|
+
storeInDB("error", typeof msg === 'object' ? jsonStringify(msg) : errorMessage, meta, fullTsStack);
|
|
334
452
|
},
|
|
335
453
|
warn: (msg: any, ...args: any[]) => {
|
|
336
454
|
const messageString = typeof msg === 'object' ? jsonStringify(msg) : String(msg);
|
|
337
|
-
|
|
338
|
-
|
|
455
|
+
const callStack = captureCallStack();
|
|
456
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
457
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
458
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
459
|
+
|
|
460
|
+
log("warn", messageString, undefined, fileLocation);
|
|
461
|
+
storeInDB("warn", messageString, undefined, fullTsStack); // Pass stringified message
|
|
339
462
|
},
|
|
340
463
|
|
|
341
464
|
// do not store debug logs in DB
|
|
342
465
|
debug: (msg: any, ...args: any[]) => {
|
|
343
|
-
|
|
466
|
+
const callStack = captureCallStack();
|
|
467
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
468
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
469
|
+
|
|
470
|
+
log("debug", String(msg), undefined, fileLocation);
|
|
344
471
|
},
|
|
345
472
|
|
|
346
473
|
fatal: (msg: any, ...args: any[]) => {
|
|
347
474
|
const messageString = typeof msg === 'object' ? jsonStringify(msg) : String(msg);
|
|
348
|
-
|
|
349
|
-
|
|
475
|
+
const callStack = captureCallStack();
|
|
476
|
+
const fullTsStack = extractFullTsStacktrace(callStack);
|
|
477
|
+
const frame = extractFirstProjectFrame(callStack);
|
|
478
|
+
const fileLocation = frame.file && frame.line ? `${frame.file}:${frame.line}` : undefined;
|
|
479
|
+
|
|
480
|
+
log("error", messageString, undefined, fileLocation);
|
|
481
|
+
storeInDB("error", messageString, undefined, fullTsStack);
|
|
350
482
|
// Exit after a brief delay to allow logs to flush
|
|
351
483
|
setTimeout(() => process.exit(1), 100);
|
|
352
484
|
},
|