queen-mq 0.12.1 → 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.
@@ -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. Logging is **disabled by default** and can be enabled via the `QUEEN_CLIENT_LOG` environment variable.
5
+ The Queen Client V2 includes comprehensive operation logging that captures every significant action performed by the client. You can either:
6
6
 
7
- ## Enabling Logging
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
- - **Disabled (default)**: Zero performance impact - all logging calls are no-ops
162
- - **Enabled**: Minimal impact - logging is asynchronous and uses `console.log/warn/error`
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
- The structured JSON format makes it easy to integrate with log aggregation tools:
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
- All logging calls check the `QUEEN_CLIENT_LOG` environment variable and are no-ops when disabled, ensuring zero performance impact by default.
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
 
@@ -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.#setupGracefulShutdown()
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) {
@@ -77,7 +84,7 @@ export class Queen {
77
84
  }
78
85
 
79
86
  #createHttpClient() {
80
- const { urls, timeoutMillis, retryAttempts, retryDelayMillis, loadBalancingStrategy, affinityHashRing, healthRetryAfterMillis, enableFailover, bearerToken } = this.#config
87
+ const { urls, timeoutMillis, retryAttempts, retryDelayMillis, loadBalancingStrategy, affinityHashRing, healthRetryAfterMillis, enableFailover, bearerToken, headers } = this.#config
81
88
 
82
89
  if (urls.length === 1) {
83
90
  // Single server
@@ -86,7 +93,8 @@ export class Queen {
86
93
  timeoutMillis,
87
94
  retryAttempts,
88
95
  retryDelayMillis,
89
- bearerToken
96
+ bearerToken,
97
+ headers
90
98
  })
91
99
  }
92
100
 
@@ -101,7 +109,8 @@ export class Queen {
101
109
  retryAttempts,
102
110
  retryDelayMillis,
103
111
  enableFailover,
104
- bearerToken
112
+ bearerToken,
113
+ headers
105
114
  })
106
115
  }
107
116
 
@@ -113,17 +122,17 @@ export class Queen {
113
122
 
114
123
  let signalReceivedCount = 0;
115
124
  const shutdown = async (signal) => {
116
- console.log(`\nReceived ${signal}, shutting down gracefully...`)
125
+ logger.log('Queen.shutdown', { signal })
117
126
  try {
118
127
  signalReceivedCount++;
119
128
  if (signalReceivedCount > 1) {
120
- console.log('Received multiple shutdown signals, exiting immediately')
129
+ logger.warn('Queen.shutdown', 'Received multiple shutdown signals, exiting immediately')
121
130
  process.exit(1)
122
131
  }
123
132
  await this.close()
124
133
  process.exit(0)
125
134
  } catch (error) {
126
- console.error('Error during shutdown:', error)
135
+ logger.error('Queen.shutdown', { error: error.message })
127
136
  process.exit(1)
128
137
  }
129
138
  }
@@ -452,18 +461,37 @@ export class Queen {
452
461
  // Graceful Shutdown
453
462
  // ===========================
454
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
+
455
486
  async close() {
456
487
  logger.log('Queen.close', 'Starting shutdown')
457
- console.log('Closing Queen client...')
458
488
 
459
489
  // Flush all buffers
460
490
  try {
461
491
  await this.#bufferManager.flushAllBuffers()
462
492
  logger.log('Queen.close', 'All buffers flushed')
463
- console.log('All buffers flushed')
464
493
  } catch (error) {
465
494
  logger.error('Queen.close', { error: error.message, phase: 'buffer-flush' })
466
- console.warn('Error flushing buffers:', error)
467
495
  }
468
496
 
469
497
  // Cleanup buffer manager
@@ -476,7 +504,6 @@ export class Queen {
476
504
  this.#shutdownHandlers = []
477
505
 
478
506
  logger.log('Queen.close', 'Client closed successfully')
479
- console.log('Queen client closed')
480
507
  }
481
508
  }
482
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.log('BufferManager.flushBuffer', { queueAddress, status: 'empty' })
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
- console.log(`Extracted ${messages.length} messages, sending to server...`)
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
- console.log(`Server responded:`, result ? `${result.length || 'N/A'} items` : 'null')
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
- console.log(`Extracted ${messages.length} messages, sending to server...`)
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
- console.log(`Server responded:`, result ? `${result.length || 'N/A'} items` : 'null')
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
- console.error(`Flush error for ${queueAddress}:`, error.message)
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.log('BufferManager.flushBuffer', { queueAddress, status: 'not-found' })
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
- console.log(`Flushing batch of up to ${batchSize} messages (${buffer.messageCount} remaining)`)
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
- console.log(`flushBuffer completed for ${queueAddress}`)
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
- console.log(`Waiting for ${this.#pendingFlushes.size} pending flushes...`)
170
+ logger.debug('BufferManager.waitForPendingFlushes', { count: this.#pendingFlushes.size })
182
171
  await Promise.all(Array.from(this.#pendingFlushes))
183
- console.log(`All pending flushes completed`)
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
- console.error('Lease renewal failed:', error)
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
- console.warn(`[TRACE FAILED] ${message.transactionId}: ${error.message}`)
369
+ logger.warn('ConsumerManager.trace', { transactionId: message.transactionId, error: error.message })
371
370
 
372
371
  return { success: false, error: error.message }
373
372
  }
@@ -12,6 +12,7 @@ export class HttpClient {
12
12
  #retryDelayMillis
13
13
  #enableFailover
14
14
  #bearerToken
15
+ #headers
15
16
 
16
17
  constructor(options = {}) {
17
18
  const {
@@ -21,7 +22,8 @@ export class HttpClient {
21
22
  retryAttempts = 3,
22
23
  retryDelayMillis = 1000,
23
24
  enableFailover = true,
24
- bearerToken = null
25
+ bearerToken = null,
26
+ headers = {}
25
27
  } = options
26
28
 
27
29
  this.#baseUrl = baseUrl
@@ -31,6 +33,7 @@ export class HttpClient {
31
33
  this.#retryDelayMillis = retryDelayMillis
32
34
  this.#enableFailover = enableFailover
33
35
  this.#bearerToken = bearerToken
36
+ this.#headers = headers || {}
34
37
 
35
38
  logger.log('HttpClient.constructor', {
36
39
  hasLoadBalancer: !!loadBalancer,
@@ -38,7 +41,8 @@ export class HttpClient {
38
41
  timeoutMillis,
39
42
  retryAttempts,
40
43
  enableFailover,
41
- hasAuth: !!bearerToken
44
+ hasAuth: !!bearerToken,
45
+ customHeaders: Object.keys(this.#headers).length
42
46
  })
43
47
  }
44
48
 
@@ -54,6 +58,7 @@ export class HttpClient {
54
58
  if (this.#bearerToken) {
55
59
  headers['Authorization'] = `Bearer ${this.#bearerToken}`
56
60
  }
61
+ Object.assign(headers, this.#headers)
57
62
 
58
63
  const options = {
59
64
  method,
@@ -191,7 +196,6 @@ export class HttpClient {
191
196
  }
192
197
 
193
198
  logger.warn('HttpClient.failover', { url, method, path, error: error.message })
194
- console.warn(`Request failed for ${url}: ${method} ${path} - ${error.message}`)
195
199
 
196
200
  // Don't retry on client errors (4xx)
197
201
  if (error.status && error.status >= 400 && error.status < 500) {
@@ -14,7 +14,10 @@ export const CLIENT_DEFAULTS = {
14
14
  affinityHashRing: 128, // Number of virtual nodes per server for affinity strategy
15
15
  enableFailover: true, // Auto-failover to other servers
16
16
  healthRetryAfterMillis: 5000, // Retry unhealthy backends after 5 seconds
17
- bearerToken: null // Bearer token for proxy authentication
17
+ bearerToken: null, // Bearer token for proxy authentication
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)
18
21
  }
19
22
 
20
23
  export const QUEUE_DEFAULTS = {
@@ -1,31 +1,28 @@
1
1
  /**
2
2
  * Logger utility for Queen Client v2
3
- * Controlled by QUEEN_CLIENT_LOG environment variable (Node.js)
4
- * or window.QUEEN_CLIENT_LOG (Browser)
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
- * Get formatted timestamp
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
- * Log an operation
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
- * Log a warning
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
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "queen-mq",
3
- "version": "0.12.1",
3
+ "version": "0.12.3-beta.1",
4
4
  "type": "module",
5
5
  "description": "High-performance C++ message queue backed by PostgreSQL",
6
6
  "main": "client-v2/index.js",