@gratheon/log-lib 1.0.1 → 2.0.1
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 +107 -16
- package/dist/logger.js +273 -54
- package/package.json +2 -2
- package/src/logger.ts +268 -55
package/README.md
CHANGED
|
@@ -6,10 +6,16 @@ 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
|
+
- **Enhanced Stack Traces**: Shows code frames with 5 lines of context around errors (dev mode)
|
|
10
|
+
- **Error Cause Chain Tracking**: Traverses and displays the full error.cause chain
|
|
11
|
+
- **Callsite Capture**: Captures where logger.error was called when error lacks stack frames
|
|
12
|
+
- **Automatic Database Creation**: Creates logs database and table if they don't exist
|
|
13
|
+
- **Non-blocking Initialization**: App starts even if logging DB fails
|
|
14
|
+
- **Connection Pool Optimization**: Suppresses known MySQL warnings for cleaner logs
|
|
15
|
+
- **Global Exception Handlers**: Captures uncaught exceptions and unhandled rejections
|
|
9
16
|
- **TypeScript Support**: Full type definitions included
|
|
10
17
|
- **Fastify Integration**: Special logger interface for Fastify framework
|
|
11
18
|
- **Flexible Metadata**: Support for structured metadata in logs
|
|
12
|
-
- **Error Handling**: Graceful handling of database connection failures
|
|
13
19
|
- **Multiple Log Levels**: info, error, warn, debug (debug logs are not persisted to DB)
|
|
14
20
|
|
|
15
21
|
## Installation
|
|
@@ -20,15 +26,9 @@ npm install @gratheon/log-lib
|
|
|
20
26
|
|
|
21
27
|
## Database Setup
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
The logger automatically creates the database and table on first initialization. No manual setup required!
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
mysql -u root -p < migrations/001-create-logs-table.sql
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
This will create:
|
|
30
|
-
- A `logs` database
|
|
31
|
-
- A `logs` table with fields: id, level, message, meta, timestamp
|
|
31
|
+
For reference, the migration script is available at `migrations/001-create-logs-table.sql`.
|
|
32
32
|
|
|
33
33
|
## Usage
|
|
34
34
|
|
|
@@ -55,17 +55,44 @@ logger.warn('Low memory warning', { available: '100MB' });
|
|
|
55
55
|
logger.error('Failed to connect to API', { endpoint: '/api/users' });
|
|
56
56
|
logger.debug('Processing item', { id: 123 }); // Not stored in DB
|
|
57
57
|
|
|
58
|
-
// Error with stack trace
|
|
58
|
+
// Error with stack trace and code frame (in dev mode)
|
|
59
59
|
try {
|
|
60
60
|
throw new Error('Something went wrong');
|
|
61
61
|
} catch (err) {
|
|
62
|
-
logger.error(err); // Logs error with stack trace
|
|
62
|
+
logger.error(err); // Logs error with stack trace, cause chain, and code frame
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Enriched error logging
|
|
66
66
|
logger.errorEnriched('Database query failed', err, { query: 'SELECT * FROM users' });
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
### Development Mode Features
|
|
70
|
+
|
|
71
|
+
Set `ENV_ID=dev` to enable enhanced error diagnostics:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ENV_ID=dev node app.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
In dev mode, you get:
|
|
78
|
+
- **Code frames**: Shows 5 lines of source code around the error location
|
|
79
|
+
- **Column markers**: Caret (^) pointing to the exact error position
|
|
80
|
+
- **Callsite capture**: When error lacks project stack frames, shows where logger was called
|
|
81
|
+
- **Enhanced debugging**: More verbose error output
|
|
82
|
+
|
|
83
|
+
Example dev mode output:
|
|
84
|
+
```
|
|
85
|
+
12:34:56 [error]: Something went wrong {"stack":"Error: Something went wrong\n at /app/src/user.ts:42:15\n..."}
|
|
86
|
+
|
|
87
|
+
Code frame:
|
|
88
|
+
40 | function processUser(user) {
|
|
89
|
+
41 | if (!user.id) {
|
|
90
|
+
> 42 | throw new Error('Something went wrong');
|
|
91
|
+
| ^
|
|
92
|
+
43 | }
|
|
93
|
+
44 | return user;
|
|
94
|
+
```
|
|
95
|
+
|
|
69
96
|
### Fastify Integration
|
|
70
97
|
|
|
71
98
|
```typescript
|
|
@@ -131,10 +158,42 @@ Compatible with Fastify's logger interface:
|
|
|
131
158
|
- `error(message: string | Error, meta?: LogMetadata)`
|
|
132
159
|
- `warn(msg: any)`
|
|
133
160
|
- `debug(msg: any)`
|
|
134
|
-
- `fatal(msg: any)` - Logs error and calls `process.exit(1)`
|
|
161
|
+
- `fatal(msg: any)` - Logs error and calls `process.exit(1)` after 100ms delay
|
|
135
162
|
- `trace(msg: any)` - No-op
|
|
136
163
|
- `child(meta: any)` - Returns the same logger instance
|
|
137
164
|
|
|
165
|
+
## Advanced Features
|
|
166
|
+
|
|
167
|
+
### Connection Pool Configuration
|
|
168
|
+
|
|
169
|
+
The logger uses an optimized connection pool:
|
|
170
|
+
- Pool size: 3 connections
|
|
171
|
+
- Max uses per connection: 200
|
|
172
|
+
- Idle timeout: 30 seconds
|
|
173
|
+
- Queue timeout: 60 seconds
|
|
174
|
+
- Automatic error suppression for known MySQL warnings
|
|
175
|
+
|
|
176
|
+
### Message Truncation
|
|
177
|
+
|
|
178
|
+
To prevent database bloat:
|
|
179
|
+
- Messages are truncated to 2000 characters
|
|
180
|
+
- Metadata is truncated to 2000 characters
|
|
181
|
+
- JSON stringification uses `fast-safe-stringify` for circular reference handling
|
|
182
|
+
|
|
183
|
+
### Async Initialization
|
|
184
|
+
|
|
185
|
+
The logger initializes asynchronously in the background:
|
|
186
|
+
```typescript
|
|
187
|
+
const { logger } = createLogger(config);
|
|
188
|
+
logger.info('App starting'); // Works immediately, DB writes happen when ready
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Environment-Specific Behavior
|
|
192
|
+
|
|
193
|
+
Set `ENV_ID` to control behavior:
|
|
194
|
+
- `ENV_ID=dev`: Enhanced diagnostics, code frames, callsite capture
|
|
195
|
+
- `ENV_ID=prod`: Production mode with minimal overhead
|
|
196
|
+
|
|
138
197
|
## Console Output Colors
|
|
139
198
|
|
|
140
199
|
- **Time**: Blue
|
|
@@ -157,11 +216,43 @@ CREATE TABLE `logs` (
|
|
|
157
216
|
|
|
158
217
|
## Error Handling
|
|
159
218
|
|
|
160
|
-
The logger
|
|
219
|
+
The logger provides comprehensive error handling:
|
|
220
|
+
|
|
221
|
+
### Automatic Database Creation
|
|
222
|
+
- Creates the `logs` database if it doesn't exist
|
|
223
|
+
- Creates the `logs` table with proper schema and indexes
|
|
224
|
+
- Non-blocking initialization - app starts even if DB fails
|
|
225
|
+
|
|
226
|
+
### Graceful Degradation
|
|
161
227
|
- Logs are always written to console
|
|
162
|
-
- Database errors are logged
|
|
163
|
-
-
|
|
164
|
-
-
|
|
228
|
+
- Database errors are logged but don't crash the application
|
|
229
|
+
- Connection pool errors are suppressed (packets out of order, inactivity warnings)
|
|
230
|
+
- Fire-and-forget database logging (no await in hot path)
|
|
231
|
+
|
|
232
|
+
### Enhanced Error Diagnostics
|
|
233
|
+
- **Error Cause Chain**: Automatically traverses and displays `error.cause` chains
|
|
234
|
+
- **Stack Trace Enhancement**: Formats and colorizes stack traces
|
|
235
|
+
- **Code Frames** (dev mode): Shows source code context around errors
|
|
236
|
+
- **Callsite Capture** (dev mode): Shows where logger was called when error lacks stack
|
|
237
|
+
|
|
238
|
+
### Global Exception Handling
|
|
239
|
+
The logger automatically registers handlers for:
|
|
240
|
+
- `uncaughtException`: Logs the error and exits gracefully (100ms delay for log flush)
|
|
241
|
+
- `unhandledRejection`: Logs the rejection and continues running
|
|
242
|
+
|
|
243
|
+
Example with error cause chain:
|
|
244
|
+
```typescript
|
|
245
|
+
try {
|
|
246
|
+
const dbError = new Error('Connection refused');
|
|
247
|
+
throw new Error('Failed to fetch user', { cause: dbError });
|
|
248
|
+
} catch (err) {
|
|
249
|
+
logger.error(err);
|
|
250
|
+
// Output:
|
|
251
|
+
// [error]: Failed to fetch user
|
|
252
|
+
// [stack trace]
|
|
253
|
+
// Cause chain: Error: Connection refused
|
|
254
|
+
}
|
|
255
|
+
```
|
|
165
256
|
|
|
166
257
|
## TypeScript Types
|
|
167
258
|
|
package/dist/logger.js
CHANGED
|
@@ -29,10 +29,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.createLogger = void 0;
|
|
30
30
|
const mysql_1 = __importStar(require("@databases/mysql"));
|
|
31
31
|
const fast_safe_stringify_1 = __importDefault(require("fast-safe-stringify"));
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const fs = __importStar(require("fs"));
|
|
33
|
+
const path = __importStar(require("path"));
|
|
34
|
+
let conn = null;
|
|
35
|
+
let dbInitialized = false;
|
|
36
|
+
async function initializeConnection(config) {
|
|
37
|
+
if (dbInitialized)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
const database = config.mysql.database || 'logs';
|
|
41
|
+
// First connect without database to create it if needed
|
|
42
|
+
const tempConn = (0, mysql_1.default)({
|
|
43
|
+
connectionString: `mysql://${config.mysql.user}:${config.mysql.password}@${config.mysql.host}:${config.mysql.port}/?connectionLimit=1&waitForConnections=true`,
|
|
44
|
+
bigIntMode: 'number',
|
|
45
|
+
});
|
|
46
|
+
await tempConn.query((0, mysql_1.sql) `CREATE DATABASE IF NOT EXISTS \`${database}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`);
|
|
47
|
+
await tempConn.dispose();
|
|
48
|
+
// Now create the main connection pool with the logs database
|
|
49
|
+
conn = (0, mysql_1.default)({
|
|
50
|
+
connectionString: `mysql://${config.mysql.user}:${config.mysql.password}@${config.mysql.host}:${config.mysql.port}/${database}?connectionLimit=3&waitForConnections=true`,
|
|
51
|
+
bigIntMode: 'number',
|
|
52
|
+
poolSize: 3,
|
|
53
|
+
maxUses: 200,
|
|
54
|
+
idleTimeoutMilliseconds: 30000,
|
|
55
|
+
queueTimeoutMilliseconds: 60000,
|
|
56
|
+
onError: (err) => {
|
|
57
|
+
// Suppress "packets out of order" and inactivity warnings
|
|
58
|
+
if (!err.message?.includes('packets out of order') &&
|
|
59
|
+
!err.message?.includes('inactivity') &&
|
|
60
|
+
!err.message?.includes('wait_timeout')) {
|
|
61
|
+
console.error(`MySQL logger connection pool error: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
// Create logs table if it doesn't exist
|
|
66
|
+
await conn.query((0, mysql_1.sql) `
|
|
67
|
+
CREATE TABLE IF NOT EXISTS \`logs\` (
|
|
68
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
69
|
+
level VARCHAR(50),
|
|
70
|
+
message TEXT,
|
|
71
|
+
meta TEXT,
|
|
72
|
+
timestamp DATETIME,
|
|
73
|
+
INDEX idx_timestamp (timestamp),
|
|
74
|
+
INDEX idx_level (level)
|
|
75
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
76
|
+
`);
|
|
77
|
+
dbInitialized = true;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.error('Failed to initialize logs database:', err);
|
|
81
|
+
// Don't throw - allow the service to start even if logging DB fails
|
|
82
|
+
}
|
|
36
83
|
}
|
|
37
84
|
function log(level, message, meta) {
|
|
38
85
|
let time = new Date().toISOString();
|
|
@@ -60,70 +107,212 @@ function log(level, message, meta) {
|
|
|
60
107
|
}
|
|
61
108
|
console.log(`${hhMMTime} [${level}]: ${message} ${meta}`);
|
|
62
109
|
}
|
|
63
|
-
function
|
|
64
|
-
if (!
|
|
65
|
-
|
|
110
|
+
function formatStack(stack) {
|
|
111
|
+
if (!stack)
|
|
112
|
+
return '';
|
|
113
|
+
// Remove first line if it duplicates the error message already printed.
|
|
114
|
+
const lines = stack.split('\n');
|
|
115
|
+
if (lines.length > 1 && lines[0].startsWith('Error')) {
|
|
116
|
+
lines.shift();
|
|
117
|
+
}
|
|
118
|
+
// Grey color for stack lines
|
|
119
|
+
return lines.map(l => `\x1b[90m${l}\x1b[0m`).join('\n');
|
|
120
|
+
}
|
|
121
|
+
function extractFirstProjectFrame(stack) {
|
|
122
|
+
if (!stack)
|
|
123
|
+
return {};
|
|
124
|
+
const lines = stack.split('\n');
|
|
125
|
+
for (const l of lines) {
|
|
126
|
+
// Match: at FunctionName (/app/src/some/file.ts:123:45)
|
|
127
|
+
const m = l.match(/\(([^()]+\.ts):(\d+):(\d+)\)/);
|
|
128
|
+
if (m) {
|
|
129
|
+
return { file: m[1], line: parseInt(m[2], 10), column: parseInt(m[3], 10) };
|
|
130
|
+
}
|
|
131
|
+
// Alternate format: at /app/src/file.ts:123:45
|
|
132
|
+
const m2 = l.match(/\s(at\s)?([^()]+\.ts):(\d+):(\d+)/);
|
|
133
|
+
if (m2) {
|
|
134
|
+
return { file: m2[2], line: parseInt(m2[3], 10), column: parseInt(m2[4], 10) };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
function buildCodeFrame(frame) {
|
|
140
|
+
if (!frame.file || frame.line == null)
|
|
141
|
+
return '';
|
|
142
|
+
try {
|
|
143
|
+
const filePath = frame.file.startsWith('/') ? frame.file : path.join(process.cwd(), frame.file);
|
|
144
|
+
if (!fs.existsSync(filePath))
|
|
145
|
+
return '';
|
|
146
|
+
const content = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
147
|
+
const start = Math.max(0, frame.line - 3);
|
|
148
|
+
const end = Math.min(content.length, frame.line + 2);
|
|
149
|
+
const lines = [];
|
|
150
|
+
for (let i = start; i < end; i++) {
|
|
151
|
+
const prefix = (i + 1 === frame.line) ? '\x1b[31m>\x1b[0m' : ' '; // highlight culprit line
|
|
152
|
+
const num = String(i + 1).padStart(4, ' ');
|
|
153
|
+
let codeLine = content[i];
|
|
154
|
+
if (i + 1 === frame.line && frame.column) {
|
|
155
|
+
// Add caret marker under column
|
|
156
|
+
const caretPad = ' '.repeat(frame.column - 1);
|
|
157
|
+
codeLine += `\n ${caretPad}\x1b[31m^\x1b[0m`;
|
|
158
|
+
}
|
|
159
|
+
lines.push(`${prefix} ${num} | ${codeLine}`);
|
|
160
|
+
}
|
|
161
|
+
return lines.join('\n');
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return '';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function hasProjectTsFrame(stack) {
|
|
168
|
+
if (!stack)
|
|
169
|
+
return false;
|
|
170
|
+
return stack.split('\n').some(l => l.includes('/src/') && l.includes('.ts'));
|
|
171
|
+
}
|
|
172
|
+
function printStackEnhanced(possibleError) {
|
|
173
|
+
if (!possibleError)
|
|
174
|
+
return;
|
|
175
|
+
const stack = possibleError.stack;
|
|
176
|
+
if (typeof stack !== 'string')
|
|
66
177
|
return;
|
|
178
|
+
let outputStack = stack;
|
|
179
|
+
if (process.env.ENV_ID === 'dev' && !hasProjectTsFrame(stack)) {
|
|
180
|
+
// Capture a callsite stack to show where logger.error was invoked
|
|
181
|
+
const callsite = new Error('__callsite__');
|
|
182
|
+
if (callsite.stack) {
|
|
183
|
+
const filtered = callsite.stack
|
|
184
|
+
.split('\n')
|
|
185
|
+
.filter(l => l.includes('/src/') && l.includes('.ts'))
|
|
186
|
+
.slice(0, 5) // keep it short
|
|
187
|
+
.join('\n');
|
|
188
|
+
if (filtered) {
|
|
189
|
+
outputStack += '\n\nCaptured callsite (added by logger):\n' + filtered;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
67
192
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`).catch(err => {
|
|
75
|
-
// Log connection errors to console only, don't crash
|
|
76
|
-
console.error(`\x1b[31m[Logger DB Error] Failed to store log in DB:\x1b[0m ${err.message}`);
|
|
77
|
-
// Optionally check if it's a connection error vs. other query error
|
|
78
|
-
if (err.code && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT')) {
|
|
79
|
-
console.warn(`\x1b[33m[Logger DB Warning] Logger DB connection failed (${err.code}). Is the DB ready?\x1b[0m`);
|
|
193
|
+
console.log(formatStack(outputStack));
|
|
194
|
+
if (process.env.ENV_ID === 'dev') {
|
|
195
|
+
const frame = extractFirstProjectFrame(outputStack);
|
|
196
|
+
const codeFrame = buildCodeFrame(frame);
|
|
197
|
+
if (codeFrame) {
|
|
198
|
+
console.log('\n\x1b[36mCode frame:\x1b[0m\n' + codeFrame + '\n');
|
|
80
199
|
}
|
|
81
|
-
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function buildCauseChain(err) {
|
|
203
|
+
const chain = [];
|
|
204
|
+
const visited = new Set();
|
|
205
|
+
let current = err;
|
|
206
|
+
while (current && typeof current === 'object' && !visited.has(current)) {
|
|
207
|
+
visited.add(current);
|
|
208
|
+
if (current !== err) {
|
|
209
|
+
const title = current.name ? `${current.name}: ${current.message}` : safeToStringMessage(current);
|
|
210
|
+
chain.push(title);
|
|
211
|
+
}
|
|
212
|
+
current = current.cause;
|
|
213
|
+
}
|
|
214
|
+
return chain;
|
|
215
|
+
}
|
|
216
|
+
function safeToStringMessage(message) {
|
|
217
|
+
if (typeof message === 'string')
|
|
218
|
+
return message;
|
|
219
|
+
if (message && typeof message === 'object') {
|
|
220
|
+
if (message.message && typeof message.message === 'string')
|
|
221
|
+
return message.message;
|
|
222
|
+
try {
|
|
223
|
+
return (0, fast_safe_stringify_1.default)(message).slice(0, 2000);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return String(message);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return String(message);
|
|
230
|
+
}
|
|
231
|
+
function safeMeta(meta) {
|
|
232
|
+
if (!meta)
|
|
233
|
+
return {};
|
|
234
|
+
return meta;
|
|
235
|
+
}
|
|
236
|
+
function storeInDB(level, message, meta) {
|
|
237
|
+
if (!conn || !dbInitialized) {
|
|
238
|
+
// Database not ready yet, skip DB logging
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const msg = safeToStringMessage(message);
|
|
243
|
+
const metaObj = safeMeta(meta);
|
|
244
|
+
const metaStr = (0, fast_safe_stringify_1.default)(metaObj).slice(0, 2000);
|
|
245
|
+
// Fire and forget; avoid awaiting in hot path. Catch errors to avoid unhandled rejection.
|
|
246
|
+
conn.query((0, mysql_1.sql) `INSERT INTO \`logs\` (level, message, meta, timestamp) VALUES (${level}, ${msg}, ${metaStr}, NOW())`).catch(e => {
|
|
247
|
+
// fallback console output only - but don't spam
|
|
248
|
+
if (process.env.ENV_ID === 'dev') {
|
|
249
|
+
console.error('Failed to persist log to DB', e);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
console.error('Unexpected failure preparing log for DB', e);
|
|
255
|
+
}
|
|
82
256
|
}
|
|
83
257
|
function createLogger(config) {
|
|
84
|
-
|
|
258
|
+
// Start initialization asynchronously but don't wait for it
|
|
259
|
+
initializeConnection(config).catch(err => {
|
|
260
|
+
console.error('Error during log database initialization:', err);
|
|
261
|
+
});
|
|
85
262
|
const logger = {
|
|
86
263
|
info: (message, meta) => {
|
|
87
|
-
|
|
88
|
-
|
|
264
|
+
const metaObj = safeMeta(meta);
|
|
265
|
+
log('info', safeToStringMessage(message), metaObj);
|
|
266
|
+
storeInDB('info', message, metaObj);
|
|
89
267
|
},
|
|
90
268
|
error: (message, meta) => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
269
|
+
const metaObj = safeMeta(meta);
|
|
270
|
+
if (message instanceof Error) {
|
|
271
|
+
const causeChain = buildCauseChain(message);
|
|
272
|
+
const enrichedMeta = { stack: message.stack, name: message.name, causeChain, ...metaObj };
|
|
273
|
+
log('error', message.message, enrichedMeta);
|
|
274
|
+
if (message.stack) {
|
|
275
|
+
printStackEnhanced(message);
|
|
276
|
+
}
|
|
277
|
+
if (causeChain.length) {
|
|
278
|
+
console.log('\x1b[35mCause chain:\x1b[0m ' + causeChain.join(' -> '));
|
|
279
|
+
}
|
|
280
|
+
storeInDB('error', message.message, enrichedMeta);
|
|
281
|
+
return;
|
|
98
282
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
log("error", messageString, meta);
|
|
104
|
-
// Store the original message or its stringified form in DB
|
|
105
|
-
storeInDB("error", typeof message === 'object' ? (0, fast_safe_stringify_1.default)(message) : message, meta);
|
|
283
|
+
const msgStr = safeToStringMessage(message);
|
|
284
|
+
log('error', msgStr, metaObj);
|
|
285
|
+
printStackEnhanced(message);
|
|
286
|
+
storeInDB('error', msgStr, metaObj);
|
|
106
287
|
},
|
|
107
288
|
errorEnriched: (message, error, meta) => {
|
|
108
|
-
const
|
|
109
|
-
if (error
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
289
|
+
const metaObj = safeMeta(meta);
|
|
290
|
+
if (error instanceof Error) {
|
|
291
|
+
const causeChain = buildCauseChain(error);
|
|
292
|
+
const enrichedMeta = { stack: error.stack, name: error.name, causeChain, ...metaObj };
|
|
293
|
+
log('error', `${message}: ${error.message}`, enrichedMeta);
|
|
294
|
+
if (error.stack) {
|
|
295
|
+
printStackEnhanced(error);
|
|
296
|
+
}
|
|
297
|
+
if (causeChain.length) {
|
|
298
|
+
console.log('\x1b[35mCause chain:\x1b[0m ' + causeChain.join(' -> '));
|
|
299
|
+
}
|
|
300
|
+
storeInDB('error', `${message}: ${error.message}`, enrichedMeta);
|
|
301
|
+
return;
|
|
116
302
|
}
|
|
117
|
-
|
|
118
|
-
|
|
303
|
+
const errStr = safeToStringMessage(error);
|
|
304
|
+
log('error', `${message}: ${errStr}`, metaObj);
|
|
305
|
+
printStackEnhanced(error);
|
|
306
|
+
storeInDB('error', `${message}: ${errStr}`, metaObj);
|
|
119
307
|
},
|
|
120
308
|
warn: (message, meta) => {
|
|
121
|
-
|
|
122
|
-
|
|
309
|
+
const metaObj = safeMeta(meta);
|
|
310
|
+
log('warn', safeToStringMessage(message), metaObj);
|
|
311
|
+
storeInDB('warn', message, metaObj);
|
|
123
312
|
},
|
|
124
313
|
// do not store debug logs in DB
|
|
125
314
|
debug: (message, meta) => {
|
|
126
|
-
log(
|
|
315
|
+
log('debug', safeToStringMessage(message), safeMeta(meta));
|
|
127
316
|
},
|
|
128
317
|
};
|
|
129
318
|
const fastifyLogger = {
|
|
@@ -153,16 +342,46 @@ function createLogger(config) {
|
|
|
153
342
|
const messageString = typeof msg === 'object' ? (0, fast_safe_stringify_1.default)(msg) : String(msg);
|
|
154
343
|
log("error", messageString);
|
|
155
344
|
storeInDB("error", messageString);
|
|
156
|
-
|
|
345
|
+
// Exit after a brief delay to allow logs to flush
|
|
346
|
+
setTimeout(() => process.exit(1), 100);
|
|
157
347
|
},
|
|
158
348
|
trace: (msg, ...args) => { },
|
|
159
349
|
child: (bindings) => {
|
|
160
350
|
return fastifyLogger;
|
|
161
351
|
},
|
|
162
352
|
};
|
|
163
|
-
// Set up
|
|
164
|
-
process.on(
|
|
165
|
-
|
|
353
|
+
// Set up global exception handlers
|
|
354
|
+
process.on('uncaughtException', function (err) {
|
|
355
|
+
// Use console.error directly to ensure we see the error even if logger fails
|
|
356
|
+
console.error('=== UNCAUGHT EXCEPTION ===');
|
|
357
|
+
console.error(err);
|
|
358
|
+
if (err && err.stack) {
|
|
359
|
+
console.error(err.stack);
|
|
360
|
+
}
|
|
361
|
+
// Also try to log through logger if available
|
|
362
|
+
try {
|
|
363
|
+
logger.error('UncaughtException', err);
|
|
364
|
+
}
|
|
365
|
+
catch (logErr) {
|
|
366
|
+
console.error('Failed to log uncaught exception:', logErr);
|
|
367
|
+
}
|
|
368
|
+
// Exit after a brief delay to allow logs to flush
|
|
369
|
+
setTimeout(() => process.exit(1), 100);
|
|
370
|
+
});
|
|
371
|
+
process.on('unhandledRejection', function (reason) {
|
|
372
|
+
// Use console.error directly
|
|
373
|
+
console.error('=== UNHANDLED REJECTION ===');
|
|
374
|
+
console.error(reason);
|
|
375
|
+
if (reason && reason.stack) {
|
|
376
|
+
console.error(reason.stack);
|
|
377
|
+
}
|
|
378
|
+
// Also try to log through logger
|
|
379
|
+
try {
|
|
380
|
+
logger.error('UnhandledRejection', reason);
|
|
381
|
+
}
|
|
382
|
+
catch (logErr) {
|
|
383
|
+
console.error('Failed to log unhandled rejection:', logErr);
|
|
384
|
+
}
|
|
166
385
|
});
|
|
167
386
|
return { logger, fastifyLogger };
|
|
168
387
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gratheon/log-lib",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Logging library with console and MySQL database persistence",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"author": "",
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@databases/mysql": "^
|
|
21
|
+
"@databases/mysql": "^7.0.0",
|
|
22
22
|
"fast-safe-stringify": "^2.1.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
package/src/logger.ts
CHANGED
|
@@ -1,14 +1,63 @@
|
|
|
1
1
|
import createConnectionPool, { sql, ConnectionPool } from "@databases/mysql";
|
|
2
2
|
import jsonStringify from "fast-safe-stringify";
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
3
5
|
import { LoggerConfig, Logger, FastifyLogger, LogMetadata } from "./types";
|
|
4
6
|
|
|
5
|
-
let conn: ConnectionPool;
|
|
7
|
+
let conn: ConnectionPool | null = null;
|
|
8
|
+
let dbInitialized = false;
|
|
6
9
|
|
|
7
|
-
function initializeConnection(config: LoggerConfig) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
async function initializeConnection(config: LoggerConfig) {
|
|
11
|
+
if (dbInitialized) return;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const database = config.mysql.database || 'logs';
|
|
15
|
+
|
|
16
|
+
// First connect without database to create it if needed
|
|
17
|
+
const tempConn = createConnectionPool({
|
|
18
|
+
connectionString: `mysql://${config.mysql.user}:${config.mysql.password}@${config.mysql.host}:${config.mysql.port}/?connectionLimit=1&waitForConnections=true`,
|
|
19
|
+
bigIntMode: 'number',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await tempConn.query(sql`CREATE DATABASE IF NOT EXISTS \`${database}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`);
|
|
23
|
+
await tempConn.dispose();
|
|
24
|
+
|
|
25
|
+
// Now create the main connection pool with the logs database
|
|
26
|
+
conn = createConnectionPool({
|
|
27
|
+
connectionString: `mysql://${config.mysql.user}:${config.mysql.password}@${config.mysql.host}:${config.mysql.port}/${database}?connectionLimit=3&waitForConnections=true`,
|
|
28
|
+
bigIntMode: 'number',
|
|
29
|
+
poolSize: 3,
|
|
30
|
+
maxUses: 200,
|
|
31
|
+
idleTimeoutMilliseconds: 30_000,
|
|
32
|
+
queueTimeoutMilliseconds: 60_000,
|
|
33
|
+
onError: (err) => {
|
|
34
|
+
// Suppress "packets out of order" and inactivity warnings
|
|
35
|
+
if (!err.message?.includes('packets out of order') &&
|
|
36
|
+
!err.message?.includes('inactivity') &&
|
|
37
|
+
!err.message?.includes('wait_timeout')) {
|
|
38
|
+
console.error(`MySQL logger connection pool error: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Create logs table if it doesn't exist
|
|
44
|
+
await conn.query(sql`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS \`logs\` (
|
|
46
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
47
|
+
level VARCHAR(50),
|
|
48
|
+
message TEXT,
|
|
49
|
+
meta TEXT,
|
|
50
|
+
timestamp DATETIME,
|
|
51
|
+
INDEX idx_timestamp (timestamp),
|
|
52
|
+
INDEX idx_level (level)
|
|
53
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
54
|
+
`);
|
|
55
|
+
|
|
56
|
+
dbInitialized = true;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('Failed to initialize logs database:', err);
|
|
59
|
+
// Don't throw - allow the service to start even if logging DB fails
|
|
60
|
+
}
|
|
12
61
|
}
|
|
13
62
|
|
|
14
63
|
function log(level: string, message: string, meta?: any) {
|
|
@@ -38,73 +87,208 @@ function log(level: string, message: string, meta?: any) {
|
|
|
38
87
|
console.log(`${hhMMTime} [${level}]: ${message} ${meta}`);
|
|
39
88
|
}
|
|
40
89
|
|
|
41
|
-
function
|
|
42
|
-
if (!
|
|
43
|
-
|
|
90
|
+
function formatStack(stack?: string): string {
|
|
91
|
+
if (!stack) return '';
|
|
92
|
+
// Remove first line if it duplicates the error message already printed.
|
|
93
|
+
const lines = stack.split('\n');
|
|
94
|
+
if (lines.length > 1 && lines[0].startsWith('Error')) {
|
|
95
|
+
lines.shift();
|
|
96
|
+
}
|
|
97
|
+
// Grey color for stack lines
|
|
98
|
+
return lines.map(l => `\x1b[90m${l}\x1b[0m`).join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function extractFirstProjectFrame(stack?: string): {file?: string, line?: number, column?: number} {
|
|
102
|
+
if (!stack) return {};
|
|
103
|
+
const lines = stack.split('\n');
|
|
104
|
+
for (const l of lines) {
|
|
105
|
+
// Match: at FunctionName (/app/src/some/file.ts:123:45)
|
|
106
|
+
const m = l.match(/\(([^()]+\.ts):(\d+):(\d+)\)/);
|
|
107
|
+
if (m) {
|
|
108
|
+
return {file: m[1], line: parseInt(m[2], 10), column: parseInt(m[3], 10)};
|
|
109
|
+
}
|
|
110
|
+
// Alternate format: at /app/src/file.ts:123:45
|
|
111
|
+
const m2 = l.match(/\s(at\s)?([^()]+\.ts):(\d+):(\d+)/);
|
|
112
|
+
if (m2) {
|
|
113
|
+
return {file: m2[2], line: parseInt(m2[3], 10), column: parseInt(m2[4], 10)};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function buildCodeFrame(frame: {file?: string, line?: number, column?: number}): string {
|
|
120
|
+
if (!frame.file || frame.line == null) return '';
|
|
121
|
+
try {
|
|
122
|
+
const filePath = frame.file.startsWith('/') ? frame.file : path.join(process.cwd(), frame.file);
|
|
123
|
+
if (!fs.existsSync(filePath)) return '';
|
|
124
|
+
const content = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
125
|
+
const start = Math.max(0, frame.line - 3);
|
|
126
|
+
const end = Math.min(content.length, frame.line + 2);
|
|
127
|
+
const lines: string[] = [];
|
|
128
|
+
for (let i = start; i < end; i++) {
|
|
129
|
+
const prefix = (i + 1 === frame.line) ? '\x1b[31m>\x1b[0m' : ' '; // highlight culprit line
|
|
130
|
+
const num = String(i + 1).padStart(4,' ');
|
|
131
|
+
let codeLine = content[i];
|
|
132
|
+
if (i + 1 === frame.line && frame.column) {
|
|
133
|
+
// Add caret marker under column
|
|
134
|
+
const caretPad = ' '.repeat(frame.column - 1);
|
|
135
|
+
codeLine += `\n ${caretPad}\x1b[31m^\x1b[0m`;
|
|
136
|
+
}
|
|
137
|
+
lines.push(`${prefix} ${num} | ${codeLine}`);
|
|
138
|
+
}
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
} catch {return '';}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function hasProjectTsFrame(stack?: string): boolean {
|
|
144
|
+
if (!stack) return false;
|
|
145
|
+
return stack.split('\n').some(l => l.includes('/src/') && l.includes('.ts'));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function printStackEnhanced(possibleError: any) {
|
|
149
|
+
if (!possibleError) return;
|
|
150
|
+
const stack = possibleError.stack;
|
|
151
|
+
if (typeof stack !== 'string') return;
|
|
152
|
+
let outputStack = stack;
|
|
153
|
+
if (process.env.ENV_ID === 'dev' && !hasProjectTsFrame(stack)) {
|
|
154
|
+
// Capture a callsite stack to show where logger.error was invoked
|
|
155
|
+
const callsite = new Error('__callsite__');
|
|
156
|
+
if (callsite.stack) {
|
|
157
|
+
const filtered = callsite.stack
|
|
158
|
+
.split('\n')
|
|
159
|
+
.filter(l => l.includes('/src/') && l.includes('.ts'))
|
|
160
|
+
.slice(0, 5) // keep it short
|
|
161
|
+
.join('\n');
|
|
162
|
+
if (filtered) {
|
|
163
|
+
outputStack += '\n\nCaptured callsite (added by logger):\n' + filtered;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
console.log(formatStack(outputStack));
|
|
168
|
+
if (process.env.ENV_ID === 'dev') {
|
|
169
|
+
const frame = extractFirstProjectFrame(outputStack);
|
|
170
|
+
const codeFrame = buildCodeFrame(frame);
|
|
171
|
+
if (codeFrame) {
|
|
172
|
+
console.log('\n\x1b[36mCode frame:\x1b[0m\n' + codeFrame + '\n');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function buildCauseChain(err: any): string[] {
|
|
178
|
+
const chain: string[] = [];
|
|
179
|
+
const visited = new Set<any>();
|
|
180
|
+
let current = err;
|
|
181
|
+
while (current && typeof current === 'object' && !visited.has(current)) {
|
|
182
|
+
visited.add(current);
|
|
183
|
+
if (current !== err) {
|
|
184
|
+
const title = current.name ? `${current.name}: ${current.message}` : safeToStringMessage(current);
|
|
185
|
+
chain.push(title);
|
|
186
|
+
}
|
|
187
|
+
current = current.cause;
|
|
188
|
+
}
|
|
189
|
+
return chain;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function safeToStringMessage(message: any): string {
|
|
193
|
+
if (typeof message === 'string') return message;
|
|
194
|
+
if (message && typeof message === 'object') {
|
|
195
|
+
if (message.message && typeof message.message === 'string') return message.message;
|
|
196
|
+
try {
|
|
197
|
+
return jsonStringify(message).slice(0, 2000);
|
|
198
|
+
} catch {
|
|
199
|
+
return String(message);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return String(message);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function safeMeta(meta: any): any {
|
|
206
|
+
if (!meta) return {};
|
|
207
|
+
return meta;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function storeInDB(level: string, message: any, meta?: any) {
|
|
211
|
+
if (!conn || !dbInitialized) {
|
|
212
|
+
// Database not ready yet, skip DB logging
|
|
44
213
|
return;
|
|
45
214
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// Optionally check if it's a connection error vs. other query error
|
|
56
|
-
if (err.code && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT')) {
|
|
57
|
-
console.warn(`\x1b[33m[Logger DB Warning] Logger DB connection failed (${err.code}). Is the DB ready?\x1b[0m`);
|
|
215
|
+
try {
|
|
216
|
+
const msg = safeToStringMessage(message);
|
|
217
|
+
const metaObj = safeMeta(meta);
|
|
218
|
+
const metaStr = jsonStringify(metaObj).slice(0, 2000);
|
|
219
|
+
// Fire and forget; avoid awaiting in hot path. Catch errors to avoid unhandled rejection.
|
|
220
|
+
conn.query(sql`INSERT INTO \`logs\` (level, message, meta, timestamp) VALUES (${level}, ${msg}, ${metaStr}, NOW())`).catch(e => {
|
|
221
|
+
// fallback console output only - but don't spam
|
|
222
|
+
if (process.env.ENV_ID === 'dev') {
|
|
223
|
+
console.error('Failed to persist log to DB', e);
|
|
58
224
|
}
|
|
59
225
|
});
|
|
226
|
+
} catch (e) {
|
|
227
|
+
console.error('Unexpected failure preparing log for DB', e);
|
|
228
|
+
}
|
|
60
229
|
}
|
|
61
230
|
|
|
62
231
|
export function createLogger(config: LoggerConfig): { logger: Logger; fastifyLogger: FastifyLogger } {
|
|
63
|
-
|
|
232
|
+
// Start initialization asynchronously but don't wait for it
|
|
233
|
+
initializeConnection(config).catch(err => {
|
|
234
|
+
console.error('Error during log database initialization:', err);
|
|
235
|
+
});
|
|
64
236
|
|
|
65
237
|
const logger: Logger = {
|
|
66
238
|
info: (message: string, meta?: LogMetadata) => {
|
|
67
|
-
|
|
68
|
-
|
|
239
|
+
const metaObj = safeMeta(meta);
|
|
240
|
+
log('info', safeToStringMessage(message), metaObj);
|
|
241
|
+
storeInDB('info', message, metaObj);
|
|
69
242
|
},
|
|
70
243
|
error: (message: string | Error | any, meta?: LogMetadata) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
244
|
+
const metaObj = safeMeta(meta);
|
|
245
|
+
if (message instanceof Error) {
|
|
246
|
+
const causeChain = buildCauseChain(message);
|
|
247
|
+
const enrichedMeta = {stack: message.stack, name: message.name, causeChain, ...metaObj};
|
|
248
|
+
log('error', message.message, enrichedMeta);
|
|
249
|
+
if (message.stack) {
|
|
250
|
+
printStackEnhanced(message);
|
|
251
|
+
}
|
|
252
|
+
if (causeChain.length) {
|
|
253
|
+
console.log('\x1b[35mCause chain:\x1b[0m ' + causeChain.join(' -> '));
|
|
254
|
+
}
|
|
255
|
+
storeInDB('error', message.message, enrichedMeta);
|
|
256
|
+
return;
|
|
78
257
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
log("error", messageString, meta);
|
|
84
|
-
// Store the original message or its stringified form in DB
|
|
85
|
-
storeInDB("error", typeof message === 'object' ? jsonStringify(message) : message, meta);
|
|
258
|
+
const msgStr = safeToStringMessage(message);
|
|
259
|
+
log('error', msgStr, metaObj);
|
|
260
|
+
printStackEnhanced(message);
|
|
261
|
+
storeInDB('error', msgStr, metaObj);
|
|
86
262
|
},
|
|
87
263
|
errorEnriched: (message: string, error: Error | any, meta?: LogMetadata) => {
|
|
88
|
-
const
|
|
89
|
-
if (error
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
264
|
+
const metaObj = safeMeta(meta);
|
|
265
|
+
if (error instanceof Error) {
|
|
266
|
+
const causeChain = buildCauseChain(error);
|
|
267
|
+
const enrichedMeta = {stack: error.stack, name: error.name, causeChain, ...metaObj};
|
|
268
|
+
log('error', `${message}: ${error.message}`, enrichedMeta);
|
|
269
|
+
if (error.stack) {
|
|
270
|
+
printStackEnhanced(error);
|
|
271
|
+
}
|
|
272
|
+
if (causeChain.length) {
|
|
273
|
+
console.log('\x1b[35mCause chain:\x1b[0m ' + causeChain.join(' -> '));
|
|
274
|
+
}
|
|
275
|
+
storeInDB('error', `${message}: ${error.message}`, enrichedMeta);
|
|
276
|
+
return;
|
|
96
277
|
}
|
|
97
|
-
|
|
98
|
-
|
|
278
|
+
const errStr = safeToStringMessage(error);
|
|
279
|
+
log('error', `${message}: ${errStr}`, metaObj);
|
|
280
|
+
printStackEnhanced(error);
|
|
281
|
+
storeInDB('error', `${message}: ${errStr}`, metaObj);
|
|
99
282
|
},
|
|
100
283
|
warn: (message: string, meta?: LogMetadata) => {
|
|
101
|
-
|
|
102
|
-
|
|
284
|
+
const metaObj = safeMeta(meta);
|
|
285
|
+
log('warn', safeToStringMessage(message), metaObj);
|
|
286
|
+
storeInDB('warn', message, metaObj);
|
|
103
287
|
},
|
|
104
288
|
|
|
105
289
|
// do not store debug logs in DB
|
|
106
290
|
debug: (message: string, meta?: LogMetadata) => {
|
|
107
|
-
log(
|
|
291
|
+
log('debug', safeToStringMessage(message), safeMeta(meta));
|
|
108
292
|
},
|
|
109
293
|
};
|
|
110
294
|
|
|
@@ -137,7 +321,8 @@ export function createLogger(config: LoggerConfig): { logger: Logger; fastifyLog
|
|
|
137
321
|
const messageString = typeof msg === 'object' ? jsonStringify(msg) : String(msg);
|
|
138
322
|
log("error", messageString);
|
|
139
323
|
storeInDB("error", messageString);
|
|
140
|
-
|
|
324
|
+
// Exit after a brief delay to allow logs to flush
|
|
325
|
+
setTimeout(() => process.exit(1), 100);
|
|
141
326
|
},
|
|
142
327
|
|
|
143
328
|
trace: (msg: any, ...args: any[]) => {},
|
|
@@ -146,9 +331,37 @@ export function createLogger(config: LoggerConfig): { logger: Logger; fastifyLog
|
|
|
146
331
|
},
|
|
147
332
|
};
|
|
148
333
|
|
|
149
|
-
// Set up
|
|
150
|
-
process.on(
|
|
151
|
-
|
|
334
|
+
// Set up global exception handlers
|
|
335
|
+
process.on('uncaughtException', function (err) {
|
|
336
|
+
// Use console.error directly to ensure we see the error even if logger fails
|
|
337
|
+
console.error('=== UNCAUGHT EXCEPTION ===');
|
|
338
|
+
console.error(err);
|
|
339
|
+
if (err && err.stack) {
|
|
340
|
+
console.error(err.stack);
|
|
341
|
+
}
|
|
342
|
+
// Also try to log through logger if available
|
|
343
|
+
try {
|
|
344
|
+
logger.error('UncaughtException', err);
|
|
345
|
+
} catch (logErr) {
|
|
346
|
+
console.error('Failed to log uncaught exception:', logErr);
|
|
347
|
+
}
|
|
348
|
+
// Exit after a brief delay to allow logs to flush
|
|
349
|
+
setTimeout(() => process.exit(1), 100);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
process.on('unhandledRejection', function (reason: any) {
|
|
353
|
+
// Use console.error directly
|
|
354
|
+
console.error('=== UNHANDLED REJECTION ===');
|
|
355
|
+
console.error(reason);
|
|
356
|
+
if (reason && reason.stack) {
|
|
357
|
+
console.error(reason.stack);
|
|
358
|
+
}
|
|
359
|
+
// Also try to log through logger
|
|
360
|
+
try {
|
|
361
|
+
logger.error('UnhandledRejection', reason);
|
|
362
|
+
} catch (logErr) {
|
|
363
|
+
console.error('Failed to log unhandled rejection:', logErr);
|
|
364
|
+
}
|
|
152
365
|
});
|
|
153
366
|
|
|
154
367
|
return { logger, fastifyLogger };
|