@arivlabs/logger 1.5.0 → 2.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/CHANGELOG.md CHANGED
@@ -5,6 +5,76 @@ All notable changes to `@arivlabs/logger` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.0] - 2026-01-21
9
+
10
+ ### Added
11
+
12
+ - **Async logging mode** (default in production) for high-throughput, non-blocking logging
13
+ - Configurable via `enableAsync: true/false` option
14
+ - `asyncBufferSize` option to control buffer size (default: 4096 bytes)
15
+ - Automatic sync mode for development, local, and test environments
16
+ - **Crash-safe logging** with synchronous flush for guaranteed delivery on exceptions
17
+ - Opt-in via `handleExceptions: true` config option
18
+ - Uses SonicBoom's `flushSync()` to ensure logs are written before process exit
19
+ - **Graceful shutdown API**
20
+ - `logger.flush()` - Synchronously flush buffered logs
21
+ - `logger.shutdown()` - Flush and close destinations (call before process exit)
22
+ - **Flexible types** - Define your own service/domain types locally, no more package updates
23
+ - **Custom base fields** - Add fields to every log via `base` config option
24
+ - **Direct pino access** - `logger.pino` property for advanced use cases
25
+
26
+ ### Changed
27
+
28
+ - **BREAKING**: Upgraded Pino from v9.6.0 to v10.2.0
29
+ - Drops Node.js 18 support (already required Node >=20)
30
+ - Fixes memory leak when using transports with `--import preload`
31
+ - **BREAKING**: Async logging is now default in production environments
32
+ - **Action required**: Add `await logger.shutdown()` to your SIGTERM handlers
33
+ - Use `enableAsync: false` to opt out if needed
34
+ - **BREAKING**: Exception handling is now opt-in via `handleExceptions: true`
35
+ - Previously registered handlers automatically; now explicit for safety
36
+ - Simplified API: removed generic type parameters (use TypeScript's type inference)
37
+ - Improved flush/shutdown reliability using proper SonicBoom methods
38
+
39
+ ### Removed
40
+
41
+ - **BREAKING**: `ServiceName` type - Define your own service types locally
42
+ - **BREAKING**: `LogDomain` type - Define your own domain types locally
43
+ - All hard-coded service and domain names removed from the package
44
+
45
+ ### Deprecated
46
+
47
+ - `createDomainLogger()` function - Use `logger.domain()` instead
48
+ - `createRequestLogger()` function - Use `logger.withContext()` instead
49
+
50
+ ### Migration Guide
51
+
52
+ ```typescript
53
+ // 1. Create logger (service/domain are now plain strings)
54
+ const logger = createLogger({ service: 'my-service' });
55
+
56
+ // 2. Add shutdown handler (required for async mode in production)
57
+ process.on('SIGTERM', async () => {
58
+ await logger.shutdown();
59
+ process.exit(0);
60
+ });
61
+
62
+ // 3. (Optional) Enable exception handling
63
+ const logger = createLogger({
64
+ service: 'my-service',
65
+ handleExceptions: true, // Opt-in for crash-safe logging
66
+ });
67
+
68
+ // 4. (Optional) Opt out of async mode
69
+ const logger = createLogger({ service: 'my-service', enableAsync: false });
70
+ ```
71
+
72
+ ## [1.5.0] - 2025-01-10
73
+
74
+ ### Added
75
+
76
+ - Added `feature-flags` to `LogDomain` type
77
+
8
78
  ## [1.4.1] - 2024-12-29
9
79
 
10
80
  ### Added
package/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # @arivlabs/logger
2
2
 
3
- Structured logging for ArivLabs services with CloudWatch support.
3
+ Structured, high-performance logging for Node.js services with CloudWatch support.
4
+
5
+ ## Features
6
+
7
+ - **Async by default in production** - Non-blocking logging for maximum throughput
8
+ - **Crash-safe logging** - Uses `pino.final()` for guaranteed delivery on exceptions
9
+ - **Flexible types** - Define your own service and domain types locally
10
+ - **Automatic redaction** - Sensitive data masked by default
11
+ - **Child loggers** - Domain-specific and request-scoped logging
12
+ - **CloudWatch-friendly** - JSON output optimized for AWS CloudWatch Insights
13
+ - **Pino v10** - Built on the fastest Node.js logger
4
14
 
5
15
  ## Installation
6
16
 
@@ -11,88 +21,142 @@ pnpm add @arivlabs/logger
11
21
  pnpm add -D pino-pretty
12
22
  ```
13
23
 
14
- ## Usage
24
+ ## Quick Start
15
25
 
16
26
  ```typescript
17
27
  import { createLogger } from '@arivlabs/logger';
18
28
 
19
- // Create a logger for your service
20
- const logger = createLogger({ service: 'api-gateway' });
29
+ const logger = createLogger({ service: 'my-service' });
21
30
 
22
- // Basic logging - intuitive style (recommended)
31
+ // Basic logging (intuitive style)
23
32
  logger.info('Server started', { port: 3000 });
24
33
 
25
34
  // Error logging - both { err } and { error } work
26
- logger.error('Request failed', { err: error }); // ✅ Works
27
- logger.error('Request failed', { error: error }); // Also works (auto-converted)
28
- // logger.error('Request failed', { error: err.message }); // ❌ Bad - pass the Error object, not .message
29
-
30
- // Also works: pino native style
31
- logger.info({ msg: 'Server started', port: 3000 });
35
+ logger.error('Request failed', { err: error });
36
+ logger.error('Request failed', { error }); // Also works (auto-converted)
32
37
 
33
38
  // Domain-specific logging
34
- const discoveryLog = logger.domain('discovery');
35
- discoveryLog.info('Job created', { jobId: '123' });
39
+ const authLogger = logger.domain('auth');
40
+ authLogger.info('User logged in', { userId: '123' });
36
41
 
37
42
  // Request context logging
38
- const reqLog = logger.withContext({
43
+ const reqLogger = logger.withContext({
39
44
  correlationId: 'abc-123',
40
45
  tenantId: 'tenant-1',
41
46
  domain: 'discovery',
42
47
  });
43
- reqLog.info('Processing request');
48
+ reqLogger.info('Processing request');
49
+
50
+ // IMPORTANT: Graceful shutdown (required for async mode)
51
+ process.on('SIGTERM', async () => {
52
+ await logger.shutdown();
53
+ process.exit(0);
54
+ });
44
55
  ```
45
56
 
46
- ## CloudWatch Insights Queries
57
+ ## Async Logging
47
58
 
48
- ```sql
49
- -- Filter by service
50
- fields @timestamp, @message
51
- | filter service = "api-gateway"
59
+ By default, the logger uses **async mode in production** for high throughput:
52
60
 
53
- -- Filter by domain
54
- fields @timestamp, @message
55
- | filter domain = "discovery"
61
+ ```typescript
62
+ const logger = createLogger({
63
+ service: 'my-service',
64
+ // enableAsync: true is default in production
65
+ });
56
66
 
57
- -- Filter errors
58
- fields @timestamp, @message
59
- | filter level = "error"
67
+ // Logs are buffered and written asynchronously
68
+ logger.info('High volume log', { requestId: '123' });
60
69
 
61
- -- Filter by tenant
62
- fields @timestamp, @message
63
- | filter tenant_id = "xxx"
70
+ // CRITICAL: Always flush on shutdown!
71
+ process.on('SIGTERM', async () => {
72
+ await logger.shutdown();
73
+ process.exit(0);
74
+ });
75
+ ```
64
76
 
65
- -- Combine filters
66
- fields @timestamp, service, domain, @message
67
- | filter service = "api-gateway" and domain = "discovery"
68
- | sort @timestamp desc
69
- | limit 100
77
+ ### Async Defaults by Environment
78
+
79
+ | Environment | `enableAsync` Default | Why |
80
+ | ----------- | --------------------- | ----------------------------- |
81
+ | production | `true` | High throughput, non-blocking |
82
+ | development | `false` | Immediate feedback during dev |
83
+ | local | `false` | Immediate feedback |
84
+ | test | `false` | Predictable test output |
85
+
86
+ ### Disabling Async Mode
87
+
88
+ ```typescript
89
+ const logger = createLogger({
90
+ service: 'my-service',
91
+ enableAsync: false, // All logs written synchronously
92
+ });
70
93
  ```
71
94
 
95
+ ### Async Configuration
96
+
97
+ ```typescript
98
+ const logger = createLogger({
99
+ service: 'my-service',
100
+ enableAsync: true,
101
+ asyncBufferSize: 4096, // Buffer size before auto-flush (default: 4096)
102
+ });
103
+ ```
104
+
105
+ ## Exception Handling (Opt-in)
106
+
107
+ For crash-safe logging of uncaught exceptions, enable `handleExceptions`:
108
+
109
+ ```typescript
110
+ const logger = createLogger({
111
+ service: 'my-service',
112
+ handleExceptions: true, // Registers uncaughtException/unhandledRejection handlers
113
+ });
114
+ ```
115
+
116
+ When enabled, the logger:
117
+
118
+ 1. Logs the error at `fatal` level
119
+ 2. Calls `flushSync()` on the SonicBoom destination to ensure the log is written
120
+ 3. Exits the process with code 1
121
+
122
+ **Note:** This is opt-in because automatic process exit behavior may not be desired in all applications.
123
+
72
124
  ## Configuration
73
125
 
74
126
  ```typescript
75
127
  const logger = createLogger({
76
- service: 'api-gateway', // Required: service name
77
- environment: 'production', // Optional: defaults to NODE_ENV
78
- level: 'info', // Optional: debug, info, warn, error
79
- pretty: false, // Optional: defaults to true in development
128
+ // Required
129
+ service: 'my-service',
130
+
131
+ // Optional
132
+ environment: 'production', // defaults to ENV or NODE_ENV
133
+ level: 'info', // defaults to 'debug' in dev, 'info' in prod
134
+ pretty: false, // defaults to true in development/local
135
+ enableAsync: true, // defaults to true in production
136
+ asyncBufferSize: 4096, // buffer size for async mode
137
+ handleExceptions: false, // opt-in for crash-safe logging
138
+
139
+ // Custom base fields (added to every log)
140
+ base: {
141
+ version: '2.0.0',
142
+ region: 'us-east-1',
143
+ },
144
+
145
+ // Custom redaction paths (in addition to defaults)
80
146
  redact: {
81
- // Optional: add custom paths to redact (in addition to defaults)
82
- paths: ['user.ssn', 'customSecret'],
83
- censor: '[MASKED]', // Optional: default is '[REDACTED]'
147
+ paths: ['user.ssn', 'payment.cardNumber'],
148
+ censor: '[MASKED]', // default: '[REDACTED]'
149
+ remove: false, // set true to remove key entirely
84
150
  },
85
151
  });
86
152
  ```
87
153
 
88
154
  ## Sensitive Data Redaction
89
155
 
90
- The logger automatically redacts common sensitive fields from logs. This protects against accidental credential exposure.
156
+ The logger automatically masks common sensitive fields:
91
157
 
92
158
  ### Default Redacted Fields
93
159
 
94
- These fields are **automatically masked** (shown as `[REDACTED]`):
95
-
96
160
  | Category | Fields |
97
161
  | ------------------- | ----------------------------------------------------- |
98
162
  | **Secrets** | `password`, `secret`, `token`, `apiKey`, `privateKey` |
@@ -101,63 +165,76 @@ These fields are **automatically masked** (shown as `[REDACTED]`):
101
165
  | **Request Headers** | `req.headers.authorization`, `req.headers.cookie` |
102
166
  | **Nested** | `*.password`, `*.secret`, `*.token`, `*.apiKey` |
103
167
 
104
- ### Adding Custom Redaction Paths
168
+ ### Adding Custom Redaction
105
169
 
106
170
  ```typescript
107
171
  const logger = createLogger({
108
- service: 'api-gateway',
172
+ service: 'my-service',
109
173
  redact: {
110
- // Add project-specific sensitive fields
111
- paths: [
112
- 'user.ssn', // Exact path
113
- 'customer.creditCard', // Exact path
114
- '*.bankAccount', // Any object with bankAccount
115
- ],
116
- censor: '[MASKED]', // Custom mask text
117
- remove: false, // Set true to remove key entirely
174
+ paths: ['user.ssn', 'payment.cardNumber', '*.bankAccount'],
118
175
  },
119
176
  });
120
177
  ```
121
178
 
122
- ## Log Format
179
+ ## Log Output Format
123
180
 
124
- JSON output (production):
181
+ **JSON (production):**
125
182
 
126
183
  ```json
127
184
  {
128
185
  "level": 30,
129
- "timestamp": "2024-01-15T10:30:00.000Z",
130
- "service": "api-gateway",
186
+ "timestamp": "2026-01-21T10:30:00.000Z",
187
+ "service": "my-service",
131
188
  "environment": "production",
132
- "domain": "discovery",
189
+ "domain": "auth",
133
190
  "correlation_id": "abc-123",
134
191
  "tenant_id": "tenant-1",
135
- "msg": "Job created",
136
- "jobId": "job-456"
192
+ "msg": "User logged in",
193
+ "userId": "user-456"
137
194
  }
138
195
  ```
139
196
 
140
- Pretty output (development):
197
+ **Pretty (development):**
141
198
 
142
199
  ```
143
- 10:30:00 Z [api-gateway:discovery] abc-123 Job created
200
+ 10:30:00 Z [my-service:auth] abc-123 User logged in
144
201
  ```
145
202
 
146
- ## Error Logging Best Practices
203
+ ## CloudWatch Insights Queries
204
+
205
+ ```sql
206
+ -- Filter by service
207
+ fields @timestamp, @message | filter service = "my-service"
208
+
209
+ -- Filter by domain
210
+ fields @timestamp, @message | filter domain = "auth"
147
211
 
148
- Pass the Error object directly - both `err` and `error` keys work:
212
+ -- Filter errors (level 50 = error)
213
+ fields @timestamp, @message | filter level >= 50
214
+
215
+ -- Filter by tenant
216
+ fields @timestamp, @message | filter tenant_id = "tenant-123"
217
+
218
+ -- Trace a request
219
+ fields @timestamp, service, domain, @message
220
+ | filter correlation_id = "abc-123"
221
+ | sort @timestamp asc
222
+ ```
223
+
224
+ ## Error Logging
225
+
226
+ Pass the Error object directly:
149
227
 
150
228
  ```typescript
151
229
  try {
152
230
  await someOperation();
153
231
  } catch (error) {
154
- // Both work - error is auto-converted to err for pino
232
+ // Both work - error is auto-converted to err
155
233
  logger.error('Operation failed', { err: error });
156
234
  logger.error('Operation failed', { error }); // Same result
157
235
 
158
- // Bad - loses error type, stack, and custom properties
159
- logger.error('Operation failed', { error: error.message });
160
- logger.error('Operation failed', { message: error.message, stack: error.stack });
236
+ // Bad - loses error type, stack, and custom properties
237
+ logger.error('Operation failed', { message: error.message });
161
238
  }
162
239
  ```
163
240
 
@@ -165,22 +242,97 @@ Pino's error serializer captures:
165
242
 
166
243
  - Error name/type (e.g., `TypeError`, `ValidationError`)
167
244
  - Error message
168
- - Stack trace
245
+ - Full stack trace
169
246
  - Custom error properties
170
247
 
171
- ## Available Domains
172
-
173
- - `discovery` - Discovery scanning
174
- - `auth` - Authentication
175
- - `connectors` - Cloud connectors
176
- - `inventory` - Resource inventory
177
- - `lineage` - Data lineage
178
- - `onboarding` - Customer onboarding
179
- - `proxy` - AI proxy
180
- - `users` - User management
181
- - `dashboard` - Analytics dashboard
182
- - `internal` - Internal APIs
183
- - `storage` - File storage
184
- - `email` - Email service
185
- - `queue` - Queue processing
186
- - `system` - System-level logs
248
+ ## API Reference
249
+
250
+ ### `createLogger(config)`
251
+
252
+ Creates a new logger instance.
253
+
254
+ ### `ArivLogger` Interface
255
+
256
+ | Method | Description |
257
+ | ----------------------- | ------------------------------------------ |
258
+ | `trace(msg, data?)` | Log at trace level |
259
+ | `debug(msg, data?)` | Log at debug level |
260
+ | `info(msg, data?)` | Log at info level |
261
+ | `warn(msg, data?)` | Log at warn level |
262
+ | `error(msg, data?)` | Log at error level |
263
+ | `fatal(msg, data?)` | Log at fatal level |
264
+ | `domain(name)` | Create child logger for domain |
265
+ | `withContext(ctx)` | Create child logger with request context |
266
+ | `child(bindings)` | Create child logger with custom bindings |
267
+ | `isLevelEnabled(level)` | Check if level is enabled |
268
+ | `flush()` | Synchronously flush buffered logs |
269
+ | `shutdown()` | Flush and close (call before process exit) |
270
+ | `pino` | Access underlying Pino logger |
271
+
272
+ ## Migration from v1.x
273
+
274
+ ### Breaking Changes
275
+
276
+ 1. **Async logging is now default in production**
277
+ - Add shutdown handler: `await logger.shutdown()`
278
+
279
+ 2. **`ServiceName` and `LogDomain` types removed**
280
+ - Define your own types locally
281
+
282
+ 3. **Config option renamed**: `async` → `enableAsync`
283
+
284
+ 4. **Exception handling is now opt-in**
285
+ - Use `handleExceptions: true` if needed
286
+
287
+ ### Migration Steps
288
+
289
+ ```typescript
290
+ // Before (v1.x)
291
+ const logger = createLogger({ service: 'api-gateway' });
292
+
293
+ // After (v2.x)
294
+ const logger = createLogger({ service: 'my-service' });
295
+
296
+ // Add shutdown handler (required for async mode)
297
+ process.on('SIGTERM', async () => {
298
+ await logger.shutdown();
299
+ process.exit(0);
300
+ });
301
+ ```
302
+
303
+ ## Performance Tips
304
+
305
+ 1. **Use `isLevelEnabled` for expensive computations:**
306
+
307
+ ```typescript
308
+ if (logger.isLevelEnabled('debug')) {
309
+ logger.debug('Details', { data: computeExpensiveData() });
310
+ }
311
+ ```
312
+
313
+ 2. **Keep log messages short** - data goes in the object:
314
+
315
+ ```typescript
316
+ // Good
317
+ logger.info('Request processed', { userId, duration, status });
318
+
319
+ // Bad
320
+ logger.info(`Request for user ${userId} took ${duration}ms with status ${status}`);
321
+ ```
322
+
323
+ 3. **Reuse domain loggers:**
324
+
325
+ ```typescript
326
+ // Good - create once, reuse
327
+ const authLogger = logger.domain('auth');
328
+ authLogger.info('Login');
329
+ authLogger.info('Logout');
330
+
331
+ // Bad - wasteful
332
+ logger.domain('auth').info('Login');
333
+ logger.domain('auth').info('Logout');
334
+ ```
335
+
336
+ ## License
337
+
338
+ MIT