@beethovn/logging 1.0.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/LICENSE +21 -0
- package/README.md +506 -0
- package/dist/core/CorrelationContextManager.d.ts +83 -0
- package/dist/core/CorrelationContextManager.d.ts.map +1 -0
- package/dist/core/CorrelationContextManager.js +102 -0
- package/dist/core/CorrelationContextManager.js.map +1 -0
- package/dist/core/Logger.d.ts +105 -0
- package/dist/core/Logger.d.ts.map +1 -0
- package/dist/core/Logger.js +210 -0
- package/dist/core/Logger.js.map +1 -0
- package/dist/core/types.d.ts +73 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +20 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/transports/ConsoleTransport.d.ts +27 -0
- package/dist/transports/ConsoleTransport.d.ts.map +1 -0
- package/dist/transports/ConsoleTransport.js +85 -0
- package/dist/transports/ConsoleTransport.js.map +1 -0
- package/dist/transports/LogTransport.d.ts +16 -0
- package/dist/transports/LogTransport.d.ts.map +1 -0
- package/dist/transports/LogTransport.js +2 -0
- package/dist/transports/LogTransport.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Beethovn Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
# @beethovn/logging
|
|
2
|
+
|
|
3
|
+
Structured logging with correlation tracking for the Beethovn monorepo.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Structured Logging**: JSON-formatted logs for easy parsing and analysis
|
|
8
|
+
- **Correlation Tracking**: Automatic correlation ID propagation across async operations
|
|
9
|
+
- **Context Management**: Track user ID, tenant ID, and custom context fields
|
|
10
|
+
- **Error Integration**: Seamless integration with `@beethovn/errors` package
|
|
11
|
+
- **Multiple Log Levels**: DEBUG, INFO, WARN, ERROR with priority filtering
|
|
12
|
+
- **Transport System**: Flexible output to console, files, or custom destinations
|
|
13
|
+
- **Child Loggers**: Create context-specific loggers with shared configuration
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @beethovn/logging
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Logger, LogLevel } from '@beethovn/logging';
|
|
25
|
+
|
|
26
|
+
const logger = new Logger({
|
|
27
|
+
level: LogLevel.INFO,
|
|
28
|
+
service: 'payment-service',
|
|
29
|
+
json: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
logger.info('Payment processed', { paymentId: '123', amount: 100 });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Core Concepts
|
|
36
|
+
|
|
37
|
+
### Log Levels
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
enum LogLevel {
|
|
41
|
+
DEBUG = 'debug', // Detailed debugging information
|
|
42
|
+
INFO = 'info', // General informational messages
|
|
43
|
+
WARN = 'warn', // Warning messages
|
|
44
|
+
ERROR = 'error', // Error messages
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Structured Log Entry
|
|
49
|
+
|
|
50
|
+
Every log entry includes:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
interface LogEntry {
|
|
54
|
+
timestamp: string; // ISO 8601 timestamp
|
|
55
|
+
level: LogLevel; // Log level
|
|
56
|
+
message: string; // Log message
|
|
57
|
+
service?: string; // Service name
|
|
58
|
+
correlationId?: string; // Request correlation ID
|
|
59
|
+
userId?: string; // User ID from context
|
|
60
|
+
tenantId?: string; // Tenant ID from context
|
|
61
|
+
metadata?: Record<string, unknown>; // Additional data
|
|
62
|
+
error?: { // Error details (if present)
|
|
63
|
+
name: string;
|
|
64
|
+
code: string;
|
|
65
|
+
message: string;
|
|
66
|
+
stack?: string;
|
|
67
|
+
metadata?: Record<string, unknown>;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Usage Examples
|
|
73
|
+
|
|
74
|
+
### Basic Logging
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const logger = new Logger({
|
|
78
|
+
level: LogLevel.INFO,
|
|
79
|
+
service: 'user-service',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
logger.debug('User lookup started', { userId: '123' });
|
|
83
|
+
logger.info('User authenticated', { userId: '123' });
|
|
84
|
+
logger.warn('Rate limit approaching', { remaining: 10 });
|
|
85
|
+
logger.error('Database connection failed');
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Error Logging
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { ErrorFactory } from '@beethovn/errors';
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await processPayment(paymentId);
|
|
95
|
+
} catch (error: unknown) {
|
|
96
|
+
const err = ErrorFactory.fromUnknown(error, 'processPayment');
|
|
97
|
+
logger.error('Payment processing failed', err, { paymentId });
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Correlation Tracking
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { CorrelationContextManager } from '@beethovn/logging';
|
|
105
|
+
|
|
106
|
+
// In your request handler
|
|
107
|
+
const correlationId = CorrelationContextManager.generateCorrelationId();
|
|
108
|
+
|
|
109
|
+
CorrelationContextManager.run(
|
|
110
|
+
{
|
|
111
|
+
correlationId,
|
|
112
|
+
userId: req.user.id,
|
|
113
|
+
tenantId: req.tenant.id,
|
|
114
|
+
},
|
|
115
|
+
async () => {
|
|
116
|
+
// All logs within this context automatically include correlation data
|
|
117
|
+
logger.info('Processing request');
|
|
118
|
+
await service.execute();
|
|
119
|
+
logger.info('Request completed');
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Child Loggers
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const mainLogger = new Logger({ service: 'api-server' });
|
|
128
|
+
|
|
129
|
+
// Create specialized logger for payment operations
|
|
130
|
+
const paymentLogger = mainLogger.child({ service: 'payment-processor' });
|
|
131
|
+
|
|
132
|
+
paymentLogger.info('Payment started', { paymentId: '123' });
|
|
133
|
+
// Logs with service: 'payment-processor'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Custom Transports
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { Logger, type LogTransport, type LogEntry } from '@beethovn/logging';
|
|
140
|
+
|
|
141
|
+
class CustomTransport implements LogTransport {
|
|
142
|
+
log(entry: LogEntry): void {
|
|
143
|
+
// Send to external logging service
|
|
144
|
+
externalService.send(entry);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const logger = new Logger();
|
|
149
|
+
logger.addTransport(new CustomTransport());
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Console Transport
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { Logger, ConsoleTransport } from '@beethovn/logging';
|
|
156
|
+
|
|
157
|
+
const logger = new Logger({ json: false }); // Disable default JSON output
|
|
158
|
+
logger.addTransport(new ConsoleTransport({
|
|
159
|
+
json: false, // Human-readable format
|
|
160
|
+
colorize: true, // ANSI color codes
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
logger.info('User logged in', { userId: '123' });
|
|
164
|
+
// Output: 2024-01-01T12:00:00.000Z [INFO] user-service User logged in
|
|
165
|
+
// Metadata: { "userId": "123" }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Correlation Context Manager
|
|
169
|
+
|
|
170
|
+
The `CorrelationContextManager` uses Node.js `AsyncLocalStorage` to maintain context across async operations without explicit parameter passing.
|
|
171
|
+
|
|
172
|
+
### API
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
class CorrelationContextManager {
|
|
176
|
+
// Run code with correlation context
|
|
177
|
+
static run<T>(context: LogContext, fn: () => T): T;
|
|
178
|
+
|
|
179
|
+
// Get current context
|
|
180
|
+
static getContext(): LogContext | undefined;
|
|
181
|
+
|
|
182
|
+
// Get correlation ID from context
|
|
183
|
+
static getCorrelationId(): string | undefined;
|
|
184
|
+
|
|
185
|
+
// Get user ID from context
|
|
186
|
+
static getUserId(): string | undefined;
|
|
187
|
+
|
|
188
|
+
// Get tenant ID from context
|
|
189
|
+
static getTenantId(): string | undefined;
|
|
190
|
+
|
|
191
|
+
// Update current context
|
|
192
|
+
static updateContext(updates: Partial<LogContext>): void;
|
|
193
|
+
|
|
194
|
+
// Generate new correlation ID
|
|
195
|
+
static generateCorrelationId(): string;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Express Middleware Example
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { CorrelationContextManager } from '@beethovn/logging';
|
|
203
|
+
|
|
204
|
+
app.use((req, res, next) => {
|
|
205
|
+
const correlationId =
|
|
206
|
+
req.headers['x-correlation-id'] as string ||
|
|
207
|
+
CorrelationContextManager.generateCorrelationId();
|
|
208
|
+
|
|
209
|
+
CorrelationContextManager.run(
|
|
210
|
+
{
|
|
211
|
+
correlationId,
|
|
212
|
+
userId: req.user?.id,
|
|
213
|
+
tenantId: req.tenant?.id,
|
|
214
|
+
},
|
|
215
|
+
() => next()
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Event Handler Example
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
eventBus.on('payment.created', async (event) => {
|
|
224
|
+
CorrelationContextManager.run(
|
|
225
|
+
{
|
|
226
|
+
correlationId: event.correlationId,
|
|
227
|
+
userId: event.userId,
|
|
228
|
+
},
|
|
229
|
+
async () => {
|
|
230
|
+
logger.info('Processing payment event', { paymentId: event.paymentId });
|
|
231
|
+
await processPayment(event);
|
|
232
|
+
logger.info('Payment event processed');
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Integration with @beethovn/errors
|
|
239
|
+
|
|
240
|
+
The logger automatically extracts and formats error details from `BeethovnError` instances:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import { ValidationError } from '@beethovn/errors';
|
|
244
|
+
|
|
245
|
+
const error = new ValidationError('Invalid email format', 'email');
|
|
246
|
+
logger.error('Validation failed', error, { userId: '123' });
|
|
247
|
+
|
|
248
|
+
// Log output includes:
|
|
249
|
+
// {
|
|
250
|
+
// "level": "error",
|
|
251
|
+
// "message": "Validation failed",
|
|
252
|
+
// "error": {
|
|
253
|
+
// "name": "ValidationError",
|
|
254
|
+
// "code": "VALIDATION_ERROR",
|
|
255
|
+
// "message": "Invalid email format",
|
|
256
|
+
// "stack": "...",
|
|
257
|
+
// "metadata": { "field": "email" }
|
|
258
|
+
// },
|
|
259
|
+
// "metadata": { "userId": "123" }
|
|
260
|
+
// }
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Configuration
|
|
264
|
+
|
|
265
|
+
### Logger Config
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
interface LoggerConfig {
|
|
269
|
+
level: LogLevel; // Minimum log level (default: INFO)
|
|
270
|
+
service: string; // Service name (default: 'beethovn')
|
|
271
|
+
json: boolean; // JSON output format (default: true)
|
|
272
|
+
timestamps: boolean; // Include timestamps (default: true)
|
|
273
|
+
colorize: boolean; // ANSI color codes (default: false)
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Log Context
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
interface LogContext {
|
|
281
|
+
correlationId?: string; // Request correlation ID
|
|
282
|
+
userId?: string; // Authenticated user ID
|
|
283
|
+
tenantId?: string; // Multi-tenant ID
|
|
284
|
+
service?: string; // Service name override
|
|
285
|
+
[key: string]: unknown; // Custom fields
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Best Practices
|
|
290
|
+
|
|
291
|
+
### 1. Use Appropriate Log Levels
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
logger.debug('Cache hit', { key }); // Development debugging
|
|
295
|
+
logger.info('User registered', { userId }); // Business events
|
|
296
|
+
logger.warn('Cache miss', { key }); // Potential issues
|
|
297
|
+
logger.error('Database error', err); // Errors requiring attention
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 2. Include Structured Metadata
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// ✅ Good: Structured metadata
|
|
304
|
+
logger.info('Order placed', {
|
|
305
|
+
orderId: '123',
|
|
306
|
+
userId: '456',
|
|
307
|
+
total: 99.99,
|
|
308
|
+
items: 3,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// ❌ Bad: Unstructured string
|
|
312
|
+
logger.info(`Order 123 placed by user 456 for $99.99`);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### 3. Always Use Correlation Context
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// ✅ Good: Context-wrapped handler
|
|
319
|
+
app.post('/orders', (req, res) => {
|
|
320
|
+
CorrelationContextManager.run(
|
|
321
|
+
{
|
|
322
|
+
correlationId: req.headers['x-correlation-id'] || generateId(),
|
|
323
|
+
userId: req.user.id,
|
|
324
|
+
},
|
|
325
|
+
async () => {
|
|
326
|
+
await createOrder(req.body);
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// ❌ Bad: No correlation tracking
|
|
332
|
+
app.post('/orders', async (req, res) => {
|
|
333
|
+
await createOrder(req.body);
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 4. Create Service-Specific Loggers
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// ✅ Good: Service-specific logger
|
|
341
|
+
const paymentLogger = new Logger({ service: 'payment-service' });
|
|
342
|
+
|
|
343
|
+
// ❌ Bad: Generic logger everywhere
|
|
344
|
+
const logger = new Logger({ service: 'app' });
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 5. Log Errors with Context
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// ✅ Good: Error with context
|
|
351
|
+
try {
|
|
352
|
+
await operation();
|
|
353
|
+
} catch (error: unknown) {
|
|
354
|
+
const err = ErrorFactory.fromUnknown(error);
|
|
355
|
+
logger.error('Operation failed', err, { operationId, userId });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ❌ Bad: Generic error log
|
|
359
|
+
try {
|
|
360
|
+
await operation();
|
|
361
|
+
} catch (error) {
|
|
362
|
+
logger.error('Error occurred', error);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Log Output Examples
|
|
367
|
+
|
|
368
|
+
### JSON Output (Default)
|
|
369
|
+
|
|
370
|
+
```json
|
|
371
|
+
{
|
|
372
|
+
"timestamp": "2024-01-01T12:00:00.000Z",
|
|
373
|
+
"level": "info",
|
|
374
|
+
"message": "Payment processed",
|
|
375
|
+
"service": "payment-service",
|
|
376
|
+
"correlationId": "cor-1704110400000-a1b2c3d4",
|
|
377
|
+
"userId": "user-123",
|
|
378
|
+
"tenantId": "tenant-456",
|
|
379
|
+
"metadata": {
|
|
380
|
+
"paymentId": "pay-789",
|
|
381
|
+
"amount": 99.99,
|
|
382
|
+
"currency": "USD"
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Formatted Output
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
2024-01-01T12:00:00.000Z [INFO] payment-service (cor-1704110400000-a1b2c3d4) Payment processed
|
|
391
|
+
Metadata: {
|
|
392
|
+
"paymentId": "pay-789",
|
|
393
|
+
"amount": 99.99,
|
|
394
|
+
"currency": "USD"
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Error Output
|
|
399
|
+
|
|
400
|
+
```json
|
|
401
|
+
{
|
|
402
|
+
"timestamp": "2024-01-01T12:00:00.000Z",
|
|
403
|
+
"level": "error",
|
|
404
|
+
"message": "Payment processing failed",
|
|
405
|
+
"service": "payment-service",
|
|
406
|
+
"correlationId": "cor-1704110400000-a1b2c3d4",
|
|
407
|
+
"metadata": {
|
|
408
|
+
"paymentId": "pay-789"
|
|
409
|
+
},
|
|
410
|
+
"error": {
|
|
411
|
+
"name": "DatabaseError",
|
|
412
|
+
"code": "DATABASE_ERROR",
|
|
413
|
+
"message": "Connection timeout",
|
|
414
|
+
"stack": "DatabaseError: Connection timeout\n at ...",
|
|
415
|
+
"metadata": {
|
|
416
|
+
"operation": "insert",
|
|
417
|
+
"table": "payments"
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Testing
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
427
|
+
import { Logger, LogLevel } from '@beethovn/logging';
|
|
428
|
+
|
|
429
|
+
describe('MyService', () => {
|
|
430
|
+
it('should log operations', () => {
|
|
431
|
+
const logger = new Logger({ json: true });
|
|
432
|
+
const logSpy = vi.spyOn(console, 'log');
|
|
433
|
+
|
|
434
|
+
logger.info('Operation completed', { id: '123' });
|
|
435
|
+
|
|
436
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
437
|
+
expect.stringContaining('"message":"Operation completed"')
|
|
438
|
+
);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Migration Guide
|
|
444
|
+
|
|
445
|
+
### From console.log
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
// Before
|
|
449
|
+
console.log('User logged in:', userId);
|
|
450
|
+
|
|
451
|
+
// After
|
|
452
|
+
logger.info('User logged in', { userId });
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### From winston/bunyan
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// Before (winston)
|
|
459
|
+
const logger = winston.createLogger({
|
|
460
|
+
transports: [new winston.transports.Console()],
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// After
|
|
464
|
+
const logger = new Logger({
|
|
465
|
+
service: 'my-service',
|
|
466
|
+
});
|
|
467
|
+
logger.addTransport(new ConsoleTransport());
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Performance Considerations
|
|
471
|
+
|
|
472
|
+
- Log level filtering happens before serialization (zero overhead for filtered logs)
|
|
473
|
+
- JSON serialization only occurs for logs that will be output
|
|
474
|
+
- Correlation context uses `AsyncLocalStorage` (minimal overhead)
|
|
475
|
+
- Child loggers share transports (no duplication)
|
|
476
|
+
|
|
477
|
+
## TypeScript Support
|
|
478
|
+
|
|
479
|
+
Full TypeScript support with strict types:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import type { LogEntry, LogContext, LoggerConfig } from '@beethovn/logging';
|
|
483
|
+
|
|
484
|
+
const config: LoggerConfig = {
|
|
485
|
+
level: LogLevel.INFO,
|
|
486
|
+
service: 'my-service',
|
|
487
|
+
json: true,
|
|
488
|
+
timestamps: true,
|
|
489
|
+
colorize: false,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const context: LogContext = {
|
|
493
|
+
correlationId: 'cor-123',
|
|
494
|
+
userId: 'user-456',
|
|
495
|
+
};
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## License
|
|
499
|
+
|
|
500
|
+
MIT
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
**Package Version:** 1.0.0
|
|
505
|
+
**Dependencies:** @beethovn/errors
|
|
506
|
+
**Node Version:** >= 18.0.0
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { LogContext } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manages correlation context across async operations
|
|
4
|
+
*
|
|
5
|
+
* Uses Node.js AsyncLocalStorage to maintain context without
|
|
6
|
+
* explicitly passing it through every function call.
|
|
7
|
+
*/
|
|
8
|
+
export declare class CorrelationContextManager {
|
|
9
|
+
private static storage;
|
|
10
|
+
/**
|
|
11
|
+
* Run a function with a specific correlation context
|
|
12
|
+
*
|
|
13
|
+
* @param context - The context to set
|
|
14
|
+
* @param fn - The function to run with this context
|
|
15
|
+
* @returns The result of the function
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* await CorrelationContextManager.run(
|
|
20
|
+
* { correlationId: 'req-123', userId: 'user-456' },
|
|
21
|
+
* async () => {
|
|
22
|
+
* // This and all nested async calls will have access to the context
|
|
23
|
+
* await processRequest();
|
|
24
|
+
* }
|
|
25
|
+
* );
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
static run<T>(context: LogContext, fn: () => T): T;
|
|
29
|
+
/**
|
|
30
|
+
* Get the current correlation context
|
|
31
|
+
*
|
|
32
|
+
* @returns The current context or undefined if not in a context
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const context = CorrelationContextManager.getContext();
|
|
37
|
+
* console.log(context?.correlationId); // 'req-123'
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
static getContext(): LogContext | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Get correlation ID from current context
|
|
43
|
+
*
|
|
44
|
+
* @returns Correlation ID or undefined
|
|
45
|
+
*/
|
|
46
|
+
static getCorrelationId(): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Get user ID from current context
|
|
49
|
+
*
|
|
50
|
+
* @returns User ID or undefined
|
|
51
|
+
*/
|
|
52
|
+
static getUserId(): string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Get tenant ID from current context
|
|
55
|
+
*
|
|
56
|
+
* @returns Tenant ID or undefined
|
|
57
|
+
*/
|
|
58
|
+
static getTenantId(): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Update the current context with new values
|
|
61
|
+
*
|
|
62
|
+
* @param updates - Partial context to merge with current
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* CorrelationContextManager.updateContext({ userId: 'user-789' });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
static updateContext(updates: Partial<LogContext>): void;
|
|
70
|
+
/**
|
|
71
|
+
* Generate a new correlation ID
|
|
72
|
+
*
|
|
73
|
+
* @returns A new correlation ID in format: cor-{timestamp}-{random}
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const id = CorrelationContextManager.generateCorrelationId();
|
|
78
|
+
* // 'cor-1704240000000-a1b2c3d4'
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
static generateCorrelationId(): string;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=CorrelationContextManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CorrelationContextManager.d.ts","sourceRoot":"","sources":["../../src/core/CorrelationContextManager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;GAKG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAuC;IAE7D;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAIlD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,UAAU,IAAI,UAAU,GAAG,SAAS;IAI3C;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,IAAI,MAAM,GAAG,SAAS;IAI7C;;;;OAIG;IACH,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,SAAS;IAItC;;;;OAIG;IACH,MAAM,CAAC,WAAW,IAAI,MAAM,GAAG,SAAS;IAIxC;;;;;;;;;OASG;IACH,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;IAOxD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,qBAAqB,IAAI,MAAM;CAKvC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
/**
|
|
3
|
+
* Manages correlation context across async operations
|
|
4
|
+
*
|
|
5
|
+
* Uses Node.js AsyncLocalStorage to maintain context without
|
|
6
|
+
* explicitly passing it through every function call.
|
|
7
|
+
*/
|
|
8
|
+
export class CorrelationContextManager {
|
|
9
|
+
static storage = new AsyncLocalStorage();
|
|
10
|
+
/**
|
|
11
|
+
* Run a function with a specific correlation context
|
|
12
|
+
*
|
|
13
|
+
* @param context - The context to set
|
|
14
|
+
* @param fn - The function to run with this context
|
|
15
|
+
* @returns The result of the function
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* await CorrelationContextManager.run(
|
|
20
|
+
* { correlationId: 'req-123', userId: 'user-456' },
|
|
21
|
+
* async () => {
|
|
22
|
+
* // This and all nested async calls will have access to the context
|
|
23
|
+
* await processRequest();
|
|
24
|
+
* }
|
|
25
|
+
* );
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
static run(context, fn) {
|
|
29
|
+
return this.storage.run(context, fn);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the current correlation context
|
|
33
|
+
*
|
|
34
|
+
* @returns The current context or undefined if not in a context
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const context = CorrelationContextManager.getContext();
|
|
39
|
+
* console.log(context?.correlationId); // 'req-123'
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
static getContext() {
|
|
43
|
+
return this.storage.getStore();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get correlation ID from current context
|
|
47
|
+
*
|
|
48
|
+
* @returns Correlation ID or undefined
|
|
49
|
+
*/
|
|
50
|
+
static getCorrelationId() {
|
|
51
|
+
return this.getContext()?.correlationId;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get user ID from current context
|
|
55
|
+
*
|
|
56
|
+
* @returns User ID or undefined
|
|
57
|
+
*/
|
|
58
|
+
static getUserId() {
|
|
59
|
+
return this.getContext()?.userId;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get tenant ID from current context
|
|
63
|
+
*
|
|
64
|
+
* @returns Tenant ID or undefined
|
|
65
|
+
*/
|
|
66
|
+
static getTenantId() {
|
|
67
|
+
return this.getContext()?.tenantId;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Update the current context with new values
|
|
71
|
+
*
|
|
72
|
+
* @param updates - Partial context to merge with current
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* CorrelationContextManager.updateContext({ userId: 'user-789' });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
static updateContext(updates) {
|
|
80
|
+
const current = this.getContext();
|
|
81
|
+
if (current) {
|
|
82
|
+
Object.assign(current, updates);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Generate a new correlation ID
|
|
87
|
+
*
|
|
88
|
+
* @returns A new correlation ID in format: cor-{timestamp}-{random}
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const id = CorrelationContextManager.generateCorrelationId();
|
|
93
|
+
* // 'cor-1704240000000-a1b2c3d4'
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
static generateCorrelationId() {
|
|
97
|
+
const timestamp = Date.now();
|
|
98
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
99
|
+
return `cor-${timestamp}-${random}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=CorrelationContextManager.js.map
|