queen-mq 0.12.2 → 0.12.3-beta.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/client-v2/LOGGING.md +63 -30
- package/client-v2/Queen.js +35 -10
- package/client-v2/buffer/BufferManager.js +11 -23
- package/client-v2/builders/QueueBuilder.js +0 -2
- package/client-v2/consumer/ConsumerManager.js +2 -3
- package/client-v2/http/HttpClient.js +0 -1
- package/client-v2/utils/defaults.js +3 -1
- package/client-v2/utils/logger.js +46 -22
- package/package.json +1 -1
package/client-v2/LOGGING.md
CHANGED
|
@@ -2,9 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The Queen Client V2 includes comprehensive operation logging that captures every significant action performed by the client.
|
|
5
|
+
The Queen Client V2 includes comprehensive operation logging that captures every significant action performed by the client. You can either:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
1. **Inject a custom logger** (pino, winston, bunyan, etc.) via the `logger` config option -- recommended for production.
|
|
8
|
+
2. **Use the built-in console logger** gated by the `QUEEN_CLIENT_LOG` environment variable -- handy for quick debugging.
|
|
9
|
+
|
|
10
|
+
## Custom Logger (Recommended)
|
|
11
|
+
|
|
12
|
+
Pass any logger instance that implements `info()`, `warn()`, and `error()` methods. When a custom logger is configured, it is always active (level filtering is controlled by your logger, not by `QUEEN_CLIENT_LOG`).
|
|
13
|
+
|
|
14
|
+
### Pino
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import pino from 'pino'
|
|
18
|
+
import { Queen } from '@punkish/queen'
|
|
19
|
+
|
|
20
|
+
const queen = new Queen({
|
|
21
|
+
urls: ['http://localhost:6632'],
|
|
22
|
+
logger: pino()
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Winston
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import winston from 'winston'
|
|
30
|
+
import { Queen } from '@punkish/queen'
|
|
31
|
+
|
|
32
|
+
const winstonLogger = winston.createLogger({
|
|
33
|
+
level: 'info',
|
|
34
|
+
transports: [new winston.transports.Console()]
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const queen = new Queen({
|
|
38
|
+
urls: ['http://localhost:6632'],
|
|
39
|
+
logger: winstonLogger
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Custom implementation
|
|
44
|
+
|
|
45
|
+
Any object with `info`, `warn`, and `error` methods works:
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
const queen = new Queen({
|
|
49
|
+
urls: ['http://localhost:6632'],
|
|
50
|
+
logger: {
|
|
51
|
+
info: (msg) => myLogSink('INFO', msg),
|
|
52
|
+
warn: (msg) => myLogSink('WARN', msg),
|
|
53
|
+
error: (msg) => myLogSink('ERROR', msg)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The `debug()` method is optional. If your logger provides it, verbose internal details (buffer flush progress, server responses, etc.) will be emitted at `debug` level. Otherwise they fall back to `info`.
|
|
59
|
+
|
|
60
|
+
## Built-in Console Logger
|
|
61
|
+
|
|
62
|
+
If no custom logger is provided, the built-in console logger can be enabled via environment variable:
|
|
8
63
|
|
|
9
64
|
```bash
|
|
10
65
|
export QUEEN_CLIENT_LOG=true
|
|
@@ -158,11 +213,13 @@ pm2 restart my-app
|
|
|
158
213
|
|
|
159
214
|
## Performance Impact
|
|
160
215
|
|
|
161
|
-
- **
|
|
162
|
-
- **
|
|
216
|
+
- **No logger configured + `QUEEN_CLIENT_LOG` unset (default)**: Zero performance impact - all logging calls are no-ops
|
|
217
|
+
- **Built-in console logger enabled**: Minimal impact
|
|
218
|
+
- **Custom logger**: Depends on the logger implementation; pino in async mode has negligible overhead
|
|
163
219
|
|
|
164
220
|
## Log Levels
|
|
165
221
|
|
|
222
|
+
- **DEBUG**: Verbose operational details (buffer flush progress, extracted message counts, server responses)
|
|
166
223
|
- **INFO**: Normal operations (most logs)
|
|
167
224
|
- **WARN**: Recoverable issues (retries, failover, network errors)
|
|
168
225
|
- **ERROR**: Operation failures (push failed, ack failed, etc.)
|
|
@@ -191,31 +248,7 @@ QUEEN_CLIENT_LOG=true node app.js 2>&1 | grep "pop"
|
|
|
191
248
|
|
|
192
249
|
## Integration with Log Aggregation
|
|
193
250
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
### Winston
|
|
197
|
-
```javascript
|
|
198
|
-
import winston from 'winston'
|
|
199
|
-
|
|
200
|
-
// Redirect console.log to Winston
|
|
201
|
-
console.log = winston.info
|
|
202
|
-
console.error = winston.error
|
|
203
|
-
console.warn = winston.warn
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### Pino
|
|
207
|
-
```javascript
|
|
208
|
-
import pino from 'pino'
|
|
209
|
-
const logger = pino()
|
|
210
|
-
|
|
211
|
-
console.log = (msg) => logger.info(msg)
|
|
212
|
-
console.error = (msg) => logger.error(msg)
|
|
213
|
-
console.warn = (msg) => logger.warn(msg)
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Datadog, CloudWatch, etc.
|
|
217
|
-
|
|
218
|
-
The ISO 8601 timestamps and JSON format are compatible with most log aggregation services.
|
|
251
|
+
When using a custom logger, integration with log aggregation services (Datadog, CloudWatch, Elastic, etc.) depends on your logger's configuration, not on Queen. For example, pino outputs NDJSON by default which is compatible with most log aggregation pipelines out of the box.
|
|
219
252
|
|
|
220
253
|
## Best Practices
|
|
221
254
|
|
|
@@ -236,5 +269,5 @@ The logging system is implemented in `utils/logger.js` and imported by all major
|
|
|
236
269
|
- `QueueBuilder.js` - Queue operations
|
|
237
270
|
- `TransactionBuilder.js` - Atomic transactions
|
|
238
271
|
|
|
239
|
-
|
|
272
|
+
When a custom logger is configured via `new Queen({ logger })`, all internal logging is routed through it. When no custom logger is provided, logging falls back to the built-in console logger gated by `QUEEN_CLIENT_LOG`.
|
|
240
273
|
|
package/client-v2/Queen.js
CHANGED
|
@@ -23,6 +23,11 @@ export class Queen {
|
|
|
23
23
|
#admin = null
|
|
24
24
|
|
|
25
25
|
constructor(config = {}) {
|
|
26
|
+
// Configure custom logger before anything else
|
|
27
|
+
if (config && typeof config === 'object' && !Array.isArray(config) && config.logger) {
|
|
28
|
+
logger.configure(config.logger)
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
logger.log('Queen.constructor', { config: typeof config === 'object' && !Array.isArray(config) ? { ...config, urls: config.urls?.length || 0 } : { type: typeof config } })
|
|
27
32
|
|
|
28
33
|
// Normalize config
|
|
@@ -34,10 +39,12 @@ export class Queen {
|
|
|
34
39
|
// Create buffer manager
|
|
35
40
|
this.#bufferManager = new BufferManager(this.#httpClient)
|
|
36
41
|
|
|
37
|
-
// Setup graceful shutdown
|
|
38
|
-
this.#
|
|
42
|
+
// Setup graceful shutdown (opt-out via handleSignals: false)
|
|
43
|
+
if (this.#config.handleSignals) {
|
|
44
|
+
this.#setupGracefulShutdown()
|
|
45
|
+
}
|
|
39
46
|
|
|
40
|
-
logger.log('Queen.constructor', { status: 'initialized', urls: this.#config.urls.length })
|
|
47
|
+
logger.log('Queen.constructor', { status: 'initialized', urls: this.#config.urls.length, handleSignals: this.#config.handleSignals })
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
#normalizeConfig(config) {
|
|
@@ -115,17 +122,17 @@ export class Queen {
|
|
|
115
122
|
|
|
116
123
|
let signalReceivedCount = 0;
|
|
117
124
|
const shutdown = async (signal) => {
|
|
118
|
-
|
|
125
|
+
logger.log('Queen.shutdown', { signal })
|
|
119
126
|
try {
|
|
120
127
|
signalReceivedCount++;
|
|
121
128
|
if (signalReceivedCount > 1) {
|
|
122
|
-
|
|
129
|
+
logger.warn('Queen.shutdown', 'Received multiple shutdown signals, exiting immediately')
|
|
123
130
|
process.exit(1)
|
|
124
131
|
}
|
|
125
132
|
await this.close()
|
|
126
133
|
process.exit(0)
|
|
127
134
|
} catch (error) {
|
|
128
|
-
|
|
135
|
+
logger.error('Queen.shutdown', { error: error.message })
|
|
129
136
|
process.exit(1)
|
|
130
137
|
}
|
|
131
138
|
}
|
|
@@ -454,18 +461,37 @@ export class Queen {
|
|
|
454
461
|
// Graceful Shutdown
|
|
455
462
|
// ===========================
|
|
456
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Register SIGINT/SIGTERM handlers so the client flushes buffers before exit.
|
|
466
|
+
* Called automatically unless handleSignals is set to false.
|
|
467
|
+
*/
|
|
468
|
+
enableGracefulShutdown() {
|
|
469
|
+
if (this.#shutdownHandlers.length > 0) return this
|
|
470
|
+
this.#setupGracefulShutdown()
|
|
471
|
+
return this
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Remove previously registered SIGINT/SIGTERM handlers.
|
|
476
|
+
* Use this when Queen is embedded in a larger application that manages its own lifecycle.
|
|
477
|
+
*/
|
|
478
|
+
disableGracefulShutdown() {
|
|
479
|
+
for (const cleanup of this.#shutdownHandlers) {
|
|
480
|
+
cleanup()
|
|
481
|
+
}
|
|
482
|
+
this.#shutdownHandlers = []
|
|
483
|
+
return this
|
|
484
|
+
}
|
|
485
|
+
|
|
457
486
|
async close() {
|
|
458
487
|
logger.log('Queen.close', 'Starting shutdown')
|
|
459
|
-
console.log('Closing Queen client...')
|
|
460
488
|
|
|
461
489
|
// Flush all buffers
|
|
462
490
|
try {
|
|
463
491
|
await this.#bufferManager.flushAllBuffers()
|
|
464
492
|
logger.log('Queen.close', 'All buffers flushed')
|
|
465
|
-
console.log('All buffers flushed')
|
|
466
493
|
} catch (error) {
|
|
467
494
|
logger.error('Queen.close', { error: error.message, phase: 'buffer-flush' })
|
|
468
|
-
console.warn('Error flushing buffers:', error)
|
|
469
495
|
}
|
|
470
496
|
|
|
471
497
|
// Cleanup buffer manager
|
|
@@ -478,7 +504,6 @@ export class Queen {
|
|
|
478
504
|
this.#shutdownHandlers = []
|
|
479
505
|
|
|
480
506
|
logger.log('Queen.close', 'Client closed successfully')
|
|
481
|
-
console.log('Queen client closed')
|
|
482
507
|
}
|
|
483
508
|
}
|
|
484
509
|
|
|
@@ -36,13 +36,11 @@ export class BufferManager {
|
|
|
36
36
|
async #flushBuffer(queueAddress) {
|
|
37
37
|
const buffer = this.#buffers.get(queueAddress)
|
|
38
38
|
if (!buffer || buffer.messageCount === 0) {
|
|
39
|
-
logger.
|
|
40
|
-
console.log(`No buffer or empty buffer for ${queueAddress}`)
|
|
39
|
+
logger.debug('BufferManager.flushBuffer', { queueAddress, status: 'empty' })
|
|
41
40
|
return
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
logger.log('BufferManager.flushBuffer', { queueAddress, messageCount: buffer.messageCount })
|
|
45
|
-
console.log(`Flushing ${buffer.messageCount} messages for ${queueAddress}`)
|
|
46
44
|
buffer.setFlushing(true)
|
|
47
45
|
|
|
48
46
|
// Create a promise for this flush and track it
|
|
@@ -50,13 +48,13 @@ export class BufferManager {
|
|
|
50
48
|
try {
|
|
51
49
|
const messages = buffer.extractMessages()
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
logger.debug('BufferManager.flushBuffer', { queueAddress, extracted: messages.length })
|
|
54
52
|
|
|
55
53
|
if (messages.length === 0) return
|
|
56
54
|
|
|
57
55
|
// Send to server
|
|
58
56
|
const result = await this.#httpClient.post('/api/v1/push', { items: messages })
|
|
59
|
-
|
|
57
|
+
logger.debug('BufferManager.flushBuffer', { queueAddress, serverResponse: result ? `${result.length || 'N/A'} items` : 'null' })
|
|
60
58
|
|
|
61
59
|
this.#flushCount++
|
|
62
60
|
logger.log('BufferManager.flushBuffer', { queueAddress, status: 'success', messagesSent: messages.length })
|
|
@@ -66,7 +64,6 @@ export class BufferManager {
|
|
|
66
64
|
|
|
67
65
|
} catch (error) {
|
|
68
66
|
logger.error('BufferManager.flushBuffer', { queueAddress, error: error.message })
|
|
69
|
-
console.error(`Flush error for ${queueAddress}:`, error.message)
|
|
70
67
|
buffer.setFlushing(false)
|
|
71
68
|
throw error
|
|
72
69
|
} finally {
|
|
@@ -94,13 +91,13 @@ export class BufferManager {
|
|
|
94
91
|
try {
|
|
95
92
|
const messages = buffer.extractMessages(batchSize)
|
|
96
93
|
|
|
97
|
-
|
|
94
|
+
logger.debug('BufferManager.flushBufferBatch', { queueAddress, extracted: messages.length })
|
|
98
95
|
|
|
99
96
|
if (messages.length === 0) return
|
|
100
97
|
|
|
101
98
|
// Send to server
|
|
102
99
|
const result = await this.#httpClient.post('/api/v1/push', { items: messages })
|
|
103
|
-
|
|
100
|
+
logger.debug('BufferManager.flushBufferBatch', { queueAddress, serverResponse: result ? `${result.length || 'N/A'} items` : 'null' })
|
|
104
101
|
|
|
105
102
|
this.#flushCount++
|
|
106
103
|
|
|
@@ -112,7 +109,7 @@ export class BufferManager {
|
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
} catch (error) {
|
|
115
|
-
|
|
112
|
+
logger.error('BufferManager.flushBufferBatch', { queueAddress, error: error.message })
|
|
116
113
|
buffer.setFlushing(false)
|
|
117
114
|
throw error
|
|
118
115
|
} finally {
|
|
@@ -129,14 +126,10 @@ export class BufferManager {
|
|
|
129
126
|
|
|
130
127
|
async flushBuffer(queueAddress) {
|
|
131
128
|
logger.log('BufferManager.flushBuffer', { queueAddress, activeBuffers: this.#buffers.size, pendingFlushes: this.#pendingFlushes.size })
|
|
132
|
-
console.log(`flushBuffer called for address: ${queueAddress}`)
|
|
133
|
-
console.log(`Active buffers:`, Array.from(this.#buffers.keys()))
|
|
134
|
-
console.log(`Pending flushes:`, this.#pendingFlushes.size)
|
|
135
129
|
|
|
136
130
|
const buffer = this.#buffers.get(queueAddress)
|
|
137
131
|
if (!buffer) {
|
|
138
|
-
logger.
|
|
139
|
-
console.log(`No buffer found for ${queueAddress}`)
|
|
132
|
+
logger.debug('BufferManager.flushBuffer', { queueAddress, status: 'not-found' })
|
|
140
133
|
await this.#waitForPendingFlushes()
|
|
141
134
|
return
|
|
142
135
|
}
|
|
@@ -149,22 +142,19 @@ export class BufferManager {
|
|
|
149
142
|
|
|
150
143
|
// Flush all messages in batches
|
|
151
144
|
while (buffer.messageCount > 0) {
|
|
152
|
-
|
|
145
|
+
logger.debug('BufferManager.flushBuffer', { queueAddress, batchSize, remaining: buffer.messageCount })
|
|
153
146
|
await this.#flushBufferBatch(queueAddress, batchSize)
|
|
154
147
|
}
|
|
155
148
|
|
|
156
149
|
// Wait for all pending flushes to complete
|
|
157
150
|
await this.#waitForPendingFlushes()
|
|
158
151
|
|
|
159
|
-
|
|
152
|
+
logger.debug('BufferManager.flushBuffer', { queueAddress, status: 'completed' })
|
|
160
153
|
}
|
|
161
154
|
|
|
162
155
|
async flushAllBuffers() {
|
|
163
156
|
const queueAddresses = Array.from(this.#buffers.keys())
|
|
164
157
|
logger.log('BufferManager.flushAllBuffers', { bufferCount: queueAddresses.length, pendingFlushes: this.#pendingFlushes.size })
|
|
165
|
-
console.log(`flushAllBuffers called, pending flushes: ${this.#pendingFlushes.size}`)
|
|
166
|
-
|
|
167
|
-
// Get all queue addresses that have buffers
|
|
168
158
|
|
|
169
159
|
// Flush each buffer in batches
|
|
170
160
|
for (const queueAddress of queueAddresses) {
|
|
@@ -172,15 +162,14 @@ export class BufferManager {
|
|
|
172
162
|
}
|
|
173
163
|
|
|
174
164
|
logger.log('BufferManager.flushAllBuffers', { status: 'completed' })
|
|
175
|
-
console.log(`flushAllBuffers completed`)
|
|
176
165
|
}
|
|
177
166
|
|
|
178
167
|
async #waitForPendingFlushes() {
|
|
179
168
|
if (this.#pendingFlushes.size === 0) return
|
|
180
169
|
|
|
181
|
-
|
|
170
|
+
logger.debug('BufferManager.waitForPendingFlushes', { count: this.#pendingFlushes.size })
|
|
182
171
|
await Promise.all(Array.from(this.#pendingFlushes))
|
|
183
|
-
|
|
172
|
+
logger.debug('BufferManager.waitForPendingFlushes', { status: 'completed' })
|
|
184
173
|
}
|
|
185
174
|
|
|
186
175
|
getStats() {
|
|
@@ -212,4 +201,3 @@ export class BufferManager {
|
|
|
212
201
|
this.#buffers.clear()
|
|
213
202
|
}
|
|
214
203
|
}
|
|
215
|
-
|
|
@@ -306,7 +306,6 @@ export class QueueBuilder {
|
|
|
306
306
|
} catch (error) {
|
|
307
307
|
// Return empty array on error instead of throwing
|
|
308
308
|
logger.error('QueueBuilder.pop', { error: error.message })
|
|
309
|
-
console.warn('Pop failed:', error.message)
|
|
310
309
|
return []
|
|
311
310
|
}
|
|
312
311
|
}
|
|
@@ -745,7 +744,6 @@ class DLQBuilder {
|
|
|
745
744
|
return result || { messages: [], total: 0 }
|
|
746
745
|
} catch (error) {
|
|
747
746
|
logger.error('DLQBuilder.get', { error: error.message })
|
|
748
|
-
console.warn('DLQ query failed:', error.message)
|
|
749
747
|
return { messages: [], total: 0 }
|
|
750
748
|
}
|
|
751
749
|
}
|
|
@@ -222,7 +222,6 @@ export class ConsumerManager {
|
|
|
222
222
|
|
|
223
223
|
if (isNetworkError) {
|
|
224
224
|
logger.warn('ConsumerManager.worker', { workerId, error: 'network', message: error.message })
|
|
225
|
-
console.warn(`Worker ${workerId}: Network error - ${error.message}`)
|
|
226
225
|
// Wait before retry
|
|
227
226
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
228
227
|
continue
|
|
@@ -296,7 +295,7 @@ export class ConsumerManager {
|
|
|
296
295
|
try {
|
|
297
296
|
await this.#queen.renew(messages)
|
|
298
297
|
} catch (error) {
|
|
299
|
-
|
|
298
|
+
logger.error('ConsumerManager.leaseRenewal', { error: error.message })
|
|
300
299
|
}
|
|
301
300
|
}, intervalMillis)
|
|
302
301
|
}
|
|
@@ -367,7 +366,7 @@ export class ConsumerManager {
|
|
|
367
366
|
error: error.message,
|
|
368
367
|
phase: 'trace-failed'
|
|
369
368
|
})
|
|
370
|
-
|
|
369
|
+
logger.warn('ConsumerManager.trace', { transactionId: message.transactionId, error: error.message })
|
|
371
370
|
|
|
372
371
|
return { success: false, error: error.message }
|
|
373
372
|
}
|
|
@@ -196,7 +196,6 @@ export class HttpClient {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
logger.warn('HttpClient.failover', { url, method, path, error: error.message })
|
|
199
|
-
console.warn(`Request failed for ${url}: ${method} ${path} - ${error.message}`)
|
|
200
199
|
|
|
201
200
|
// Don't retry on client errors (4xx)
|
|
202
201
|
if (error.status && error.status >= 400 && error.status < 500) {
|
|
@@ -15,7 +15,9 @@ export const CLIENT_DEFAULTS = {
|
|
|
15
15
|
enableFailover: true, // Auto-failover to other servers
|
|
16
16
|
healthRetryAfterMillis: 5000, // Retry unhealthy backends after 5 seconds
|
|
17
17
|
bearerToken: null, // Bearer token for proxy authentication
|
|
18
|
-
headers: {}
|
|
18
|
+
headers: {}, // Custom headers to include in every request
|
|
19
|
+
handleSignals: true, // Register SIGINT/SIGTERM handlers (disable when used as a library)
|
|
20
|
+
logger: null // Custom logger instance (must implement info/warn/error)
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export const QUEUE_DEFAULTS = {
|
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logger utility for Queen Client v2
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Supports pluggable logger backends (pino, winston, bunyan, etc.).
|
|
5
|
+
* When no custom logger is configured, falls back to console-based logging
|
|
6
|
+
* gated by the QUEEN_CLIENT_LOG environment variable (Node.js)
|
|
7
|
+
* or window.QUEEN_CLIENT_LOG (Browser).
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
const LOG_ENABLED = (() => {
|
|
8
|
-
// Node.js environment
|
|
9
11
|
if (typeof process !== 'undefined' && process.env) {
|
|
10
12
|
return process.env.QUEEN_CLIENT_LOG === 'true'
|
|
11
13
|
}
|
|
12
|
-
// Browser environment
|
|
13
14
|
if (typeof window !== 'undefined') {
|
|
14
15
|
return window.QUEEN_CLIENT_LOG === true
|
|
15
16
|
}
|
|
16
17
|
return false
|
|
17
18
|
})()
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*/
|
|
20
|
+
let customLogger = null
|
|
21
|
+
|
|
22
22
|
function getTimestamp() {
|
|
23
23
|
return new Date().toISOString()
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
* Format log message with timestamp and operation
|
|
28
|
-
*/
|
|
29
26
|
function formatLog(operation, details, level = 'INFO') {
|
|
30
27
|
const timestamp = getTimestamp()
|
|
31
28
|
const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
|
|
@@ -33,33 +30,60 @@ function formatLog(operation, details, level = 'INFO') {
|
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
|
-
*
|
|
33
|
+
* Configure a custom logger backend.
|
|
34
|
+
* The logger must implement: info(msg), warn(msg), error(msg).
|
|
35
|
+
* debug(msg) is optional and falls back to info(msg) if missing.
|
|
36
|
+
* When a custom logger is set, it is always active (no env var gating).
|
|
37
|
+
* @param {object} logger - Logger instance (e.g. pino(), winston.createLogger())
|
|
37
38
|
*/
|
|
39
|
+
export function configure(logger) {
|
|
40
|
+
if (logger && typeof logger.info !== 'function') {
|
|
41
|
+
throw new Error('Custom logger must implement info(), warn(), and error() methods')
|
|
42
|
+
}
|
|
43
|
+
customLogger = logger
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
export function log(operation, details) {
|
|
47
|
+
if (customLogger) {
|
|
48
|
+
const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
|
|
49
|
+
customLogger.info(`[${operation}] ${detailsStr}`)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
39
52
|
if (!LOG_ENABLED) return
|
|
40
53
|
console.log(formatLog(operation, details))
|
|
41
54
|
}
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
export function debug(operation, details) {
|
|
57
|
+
if (customLogger) {
|
|
58
|
+
const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
|
|
59
|
+
const fn = customLogger.debug || customLogger.info
|
|
60
|
+
fn.call(customLogger, `[${operation}] ${detailsStr}`)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
if (!LOG_ENABLED) return
|
|
64
|
+
console.log(formatLog(operation, details, 'DEBUG'))
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
export function warn(operation, details) {
|
|
68
|
+
if (customLogger) {
|
|
69
|
+
const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
|
|
70
|
+
customLogger.warn(`[${operation}] ${detailsStr}`)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
47
73
|
if (!LOG_ENABLED) return
|
|
48
74
|
console.warn(formatLog(operation, details, 'WARN'))
|
|
49
75
|
}
|
|
50
76
|
|
|
51
|
-
/**
|
|
52
|
-
* Log an error
|
|
53
|
-
*/
|
|
54
77
|
export function error(operation, details) {
|
|
78
|
+
if (customLogger) {
|
|
79
|
+
const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
|
|
80
|
+
customLogger.error(`[${operation}] ${detailsStr}`)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
55
83
|
if (!LOG_ENABLED) return
|
|
56
84
|
console.error(formatLog(operation, details, 'ERROR'))
|
|
57
85
|
}
|
|
58
86
|
|
|
59
|
-
/**
|
|
60
|
-
* Check if logging is enabled
|
|
61
|
-
*/
|
|
62
87
|
export function isEnabled() {
|
|
63
|
-
return LOG_ENABLED
|
|
88
|
+
return customLogger != null || LOG_ENABLED
|
|
64
89
|
}
|
|
65
|
-
|