queen-mq 0.12.3-beta.1 → 0.12.4
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/buffer/BufferManager.js +1 -0
- package/client-v2/utils/logger.js +8 -0
- package/package.json +1 -1
- package/test-v2/logger.js +312 -0
- package/test-v2/run.js +3 -1
|
@@ -153,6 +153,7 @@ export class BufferManager {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
async flushAllBuffers() {
|
|
156
|
+
// Get all queue addresses that have buffers
|
|
156
157
|
const queueAddresses = Array.from(this.#buffers.keys())
|
|
157
158
|
logger.log('BufferManager.flushAllBuffers', { bufferCount: queueAddresses.length, pendingFlushes: this.#pendingFlushes.size })
|
|
158
159
|
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const LOG_ENABLED = (() => {
|
|
11
|
+
// Node.js environment
|
|
11
12
|
if (typeof process !== 'undefined' && process.env) {
|
|
12
13
|
return process.env.QUEEN_CLIENT_LOG === 'true'
|
|
13
14
|
}
|
|
15
|
+
// Browser environment
|
|
14
16
|
if (typeof window !== 'undefined') {
|
|
15
17
|
return window.QUEEN_CLIENT_LOG === true
|
|
16
18
|
}
|
|
@@ -23,6 +25,9 @@ function getTimestamp() {
|
|
|
23
25
|
return new Date().toISOString()
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Format log message with timestamp and operation
|
|
30
|
+
*/
|
|
26
31
|
function formatLog(operation, details, level = 'INFO') {
|
|
27
32
|
const timestamp = getTimestamp()
|
|
28
33
|
const detailsStr = typeof details === 'object' ? JSON.stringify(details) : details
|
|
@@ -84,6 +89,9 @@ export function error(operation, details) {
|
|
|
84
89
|
console.error(formatLog(operation, details, 'ERROR'))
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Check if logging is enabled
|
|
94
|
+
*/
|
|
87
95
|
export function isEnabled() {
|
|
88
96
|
return customLogger != null || LOG_ENABLED
|
|
89
97
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the pluggable logger system in utils/logger.js:
|
|
5
|
+
* - Default console-based logging (gated by QUEEN_CLIENT_LOG)
|
|
6
|
+
* - Custom logger injection via configure()
|
|
7
|
+
* - Level mapping (log->info, warn->warn, error->error, debug->debug)
|
|
8
|
+
* - debug() fallback to info() when custom logger has no debug()
|
|
9
|
+
* - configure(null) resets to default
|
|
10
|
+
* - configure() validation
|
|
11
|
+
* - isEnabled() behavior
|
|
12
|
+
*
|
|
13
|
+
* These are pure unit tests — no Queen server required.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as logger from '../client-v2/utils/logger.js'
|
|
17
|
+
|
|
18
|
+
function createMockLogger() {
|
|
19
|
+
const calls = { info: [], warn: [], error: [], debug: [] }
|
|
20
|
+
return {
|
|
21
|
+
calls,
|
|
22
|
+
info: (msg) => calls.info.push(msg),
|
|
23
|
+
warn: (msg) => calls.warn.push(msg),
|
|
24
|
+
error: (msg) => calls.error.push(msg),
|
|
25
|
+
debug: (msg) => calls.debug.push(msg)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createMockLoggerWithoutDebug() {
|
|
30
|
+
const calls = { info: [], warn: [], error: [] }
|
|
31
|
+
return {
|
|
32
|
+
calls,
|
|
33
|
+
info: (msg) => calls.info.push(msg),
|
|
34
|
+
warn: (msg) => calls.warn.push(msg),
|
|
35
|
+
error: (msg) => calls.error.push(msg)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Test that configure() accepts a valid logger and routes log() to info()
|
|
41
|
+
*/
|
|
42
|
+
export async function testLoggerCustomLogRouting(_client) {
|
|
43
|
+
const mock = createMockLogger()
|
|
44
|
+
logger.configure(mock)
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
logger.log('TestOp', 'hello')
|
|
48
|
+
logger.log('TestOp', { key: 'value' })
|
|
49
|
+
|
|
50
|
+
if (mock.calls.info.length !== 2) {
|
|
51
|
+
return { success: false, message: `Expected 2 info calls, got ${mock.calls.info.length}` }
|
|
52
|
+
}
|
|
53
|
+
if (!mock.calls.info[0].includes('[TestOp]') || !mock.calls.info[0].includes('hello')) {
|
|
54
|
+
return { success: false, message: `Unexpected info message: ${mock.calls.info[0]}` }
|
|
55
|
+
}
|
|
56
|
+
if (!mock.calls.info[1].includes('"key":"value"')) {
|
|
57
|
+
return { success: false, message: `Object details not serialized: ${mock.calls.info[1]}` }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { success: true, message: 'log() routes to custom logger info()' }
|
|
61
|
+
} finally {
|
|
62
|
+
logger.configure(null)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Test that warn() routes to custom logger warn()
|
|
68
|
+
*/
|
|
69
|
+
export async function testLoggerCustomWarnRouting(_client) {
|
|
70
|
+
const mock = createMockLogger()
|
|
71
|
+
logger.configure(mock)
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
logger.warn('WarnOp', 'something bad')
|
|
75
|
+
logger.warn('WarnOp', { code: 42 })
|
|
76
|
+
|
|
77
|
+
if (mock.calls.warn.length !== 2) {
|
|
78
|
+
return { success: false, message: `Expected 2 warn calls, got ${mock.calls.warn.length}` }
|
|
79
|
+
}
|
|
80
|
+
if (!mock.calls.warn[0].includes('[WarnOp]')) {
|
|
81
|
+
return { success: false, message: `Missing operation in warn: ${mock.calls.warn[0]}` }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { success: true, message: 'warn() routes to custom logger warn()' }
|
|
85
|
+
} finally {
|
|
86
|
+
logger.configure(null)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Test that error() routes to custom logger error()
|
|
92
|
+
*/
|
|
93
|
+
export async function testLoggerCustomErrorRouting(_client) {
|
|
94
|
+
const mock = createMockLogger()
|
|
95
|
+
logger.configure(mock)
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
logger.error('ErrOp', { error: 'boom' })
|
|
99
|
+
|
|
100
|
+
if (mock.calls.error.length !== 1) {
|
|
101
|
+
return { success: false, message: `Expected 1 error call, got ${mock.calls.error.length}` }
|
|
102
|
+
}
|
|
103
|
+
if (!mock.calls.error[0].includes('[ErrOp]') || !mock.calls.error[0].includes('boom')) {
|
|
104
|
+
return { success: false, message: `Unexpected error message: ${mock.calls.error[0]}` }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { success: true, message: 'error() routes to custom logger error()' }
|
|
108
|
+
} finally {
|
|
109
|
+
logger.configure(null)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Test that debug() routes to custom logger debug() when available
|
|
115
|
+
*/
|
|
116
|
+
export async function testLoggerCustomDebugRouting(_client) {
|
|
117
|
+
const mock = createMockLogger()
|
|
118
|
+
logger.configure(mock)
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
logger.debug('DebugOp', 'verbose detail')
|
|
122
|
+
|
|
123
|
+
if (mock.calls.debug.length !== 1) {
|
|
124
|
+
return { success: false, message: `Expected 1 debug call, got ${mock.calls.debug.length}` }
|
|
125
|
+
}
|
|
126
|
+
if (!mock.calls.debug[0].includes('[DebugOp]')) {
|
|
127
|
+
return { success: false, message: `Missing operation in debug: ${mock.calls.debug[0]}` }
|
|
128
|
+
}
|
|
129
|
+
// info() should NOT have been called
|
|
130
|
+
if (mock.calls.info.length !== 0) {
|
|
131
|
+
return { success: false, message: `debug() should not fall back to info() when debug() exists` }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { success: true, message: 'debug() routes to custom logger debug()' }
|
|
135
|
+
} finally {
|
|
136
|
+
logger.configure(null)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Test that debug() falls back to info() when custom logger has no debug()
|
|
142
|
+
*/
|
|
143
|
+
export async function testLoggerDebugFallbackToInfo(_client) {
|
|
144
|
+
const mock = createMockLoggerWithoutDebug()
|
|
145
|
+
logger.configure(mock)
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
logger.debug('FallbackOp', 'should go to info')
|
|
149
|
+
|
|
150
|
+
if (mock.calls.info.length !== 1) {
|
|
151
|
+
return { success: false, message: `Expected 1 info call (fallback), got ${mock.calls.info.length}` }
|
|
152
|
+
}
|
|
153
|
+
if (!mock.calls.info[0].includes('[FallbackOp]')) {
|
|
154
|
+
return { success: false, message: `Missing operation in fallback: ${mock.calls.info[0]}` }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { success: true, message: 'debug() falls back to info() when no debug() on logger' }
|
|
158
|
+
} finally {
|
|
159
|
+
logger.configure(null)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Test that configure(null) resets to default console behavior
|
|
165
|
+
*/
|
|
166
|
+
export async function testLoggerConfigureNull(_client) {
|
|
167
|
+
const mock = createMockLogger()
|
|
168
|
+
logger.configure(mock)
|
|
169
|
+
|
|
170
|
+
// Verify custom logger is active
|
|
171
|
+
logger.log('Before', 'test')
|
|
172
|
+
if (mock.calls.info.length !== 1) {
|
|
173
|
+
return { success: false, message: 'Custom logger not active before reset' }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Reset
|
|
177
|
+
logger.configure(null)
|
|
178
|
+
|
|
179
|
+
// Now log should NOT go to mock anymore
|
|
180
|
+
logger.log('After', 'test')
|
|
181
|
+
if (mock.calls.info.length !== 1) {
|
|
182
|
+
return { success: false, message: `Expected still 1 info call after reset, got ${mock.calls.info.length}` }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { success: true, message: 'configure(null) resets to default' }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Test that configure() throws on invalid logger (missing info method)
|
|
190
|
+
*/
|
|
191
|
+
export async function testLoggerConfigureValidation(_client) {
|
|
192
|
+
try {
|
|
193
|
+
logger.configure({ warn: () => {}, error: () => {} })
|
|
194
|
+
return { success: false, message: 'Should have thrown for logger missing info()' }
|
|
195
|
+
} catch (e) {
|
|
196
|
+
if (!e.message.includes('info()')) {
|
|
197
|
+
return { success: false, message: `Wrong error message: ${e.message}` }
|
|
198
|
+
}
|
|
199
|
+
} finally {
|
|
200
|
+
logger.configure(null)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Empty object should also fail
|
|
204
|
+
try {
|
|
205
|
+
logger.configure({})
|
|
206
|
+
return { success: false, message: 'Should have thrown for empty object' }
|
|
207
|
+
} catch (e) {
|
|
208
|
+
// expected
|
|
209
|
+
} finally {
|
|
210
|
+
logger.configure(null)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { success: true, message: 'configure() validates logger interface' }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Test isEnabled() returns true when custom logger is set
|
|
218
|
+
*/
|
|
219
|
+
export async function testLoggerIsEnabledWithCustom(_client) {
|
|
220
|
+
// Without custom logger and without QUEEN_CLIENT_LOG, isEnabled depends on env
|
|
221
|
+
const beforeCustom = logger.isEnabled()
|
|
222
|
+
|
|
223
|
+
const mock = createMockLogger()
|
|
224
|
+
logger.configure(mock)
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
if (!logger.isEnabled()) {
|
|
228
|
+
return { success: false, message: 'isEnabled() should return true with custom logger' }
|
|
229
|
+
}
|
|
230
|
+
return { success: true, message: `isEnabled() returns true with custom logger (was ${beforeCustom} before)` }
|
|
231
|
+
} finally {
|
|
232
|
+
logger.configure(null)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Test that levels are isolated — log() only hits info(), not warn() or error()
|
|
238
|
+
*/
|
|
239
|
+
export async function testLoggerLevelIsolation(_client) {
|
|
240
|
+
const mock = createMockLogger()
|
|
241
|
+
logger.configure(mock)
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
logger.log('Op', 'info msg')
|
|
245
|
+
logger.warn('Op', 'warn msg')
|
|
246
|
+
logger.error('Op', 'error msg')
|
|
247
|
+
logger.debug('Op', 'debug msg')
|
|
248
|
+
|
|
249
|
+
if (mock.calls.info.length !== 1) {
|
|
250
|
+
return { success: false, message: `info: expected 1, got ${mock.calls.info.length}` }
|
|
251
|
+
}
|
|
252
|
+
if (mock.calls.warn.length !== 1) {
|
|
253
|
+
return { success: false, message: `warn: expected 1, got ${mock.calls.warn.length}` }
|
|
254
|
+
}
|
|
255
|
+
if (mock.calls.error.length !== 1) {
|
|
256
|
+
return { success: false, message: `error: expected 1, got ${mock.calls.error.length}` }
|
|
257
|
+
}
|
|
258
|
+
if (mock.calls.debug.length !== 1) {
|
|
259
|
+
return { success: false, message: `debug: expected 1, got ${mock.calls.debug.length}` }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return { success: true, message: 'Each level routes to exactly its corresponding method' }
|
|
263
|
+
} finally {
|
|
264
|
+
logger.configure(null)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Test that string details are passed through without JSON.stringify
|
|
270
|
+
*/
|
|
271
|
+
export async function testLoggerStringDetails(_client) {
|
|
272
|
+
const mock = createMockLogger()
|
|
273
|
+
logger.configure(mock)
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
logger.log('Op', 'plain string')
|
|
277
|
+
|
|
278
|
+
const msg = mock.calls.info[0]
|
|
279
|
+
if (!msg.includes('plain string')) {
|
|
280
|
+
return { success: false, message: `String details not passed through: ${msg}` }
|
|
281
|
+
}
|
|
282
|
+
// Should NOT be double-quoted as JSON
|
|
283
|
+
if (msg.includes('"plain string"')) {
|
|
284
|
+
return { success: false, message: `String was JSON-serialized: ${msg}` }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { success: true, message: 'String details passed as-is' }
|
|
288
|
+
} finally {
|
|
289
|
+
logger.configure(null)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Test that object details are JSON-serialized
|
|
295
|
+
*/
|
|
296
|
+
export async function testLoggerObjectDetails(_client) {
|
|
297
|
+
const mock = createMockLogger()
|
|
298
|
+
logger.configure(mock)
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
logger.log('Op', { foo: 'bar', count: 3 })
|
|
302
|
+
|
|
303
|
+
const msg = mock.calls.info[0]
|
|
304
|
+
if (!msg.includes('"foo":"bar"') || !msg.includes('"count":3')) {
|
|
305
|
+
return { success: false, message: `Object not serialized correctly: ${msg}` }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { success: true, message: 'Object details JSON-serialized' }
|
|
309
|
+
} finally {
|
|
310
|
+
logger.configure(null)
|
|
311
|
+
}
|
|
312
|
+
}
|
package/test-v2/run.js
CHANGED
|
@@ -12,6 +12,7 @@ import * as subscriptionTests from './subscription.js'
|
|
|
12
12
|
import * as maintenanceTests from './maintenance.js'
|
|
13
13
|
import * as retentionTests from './retention.js'
|
|
14
14
|
import * as bootstrapTests from './bootstrap.js'
|
|
15
|
+
import * as loggerTests from './logger.js'
|
|
15
16
|
import { LoadBalancer } from '../client-v2/http/LoadBalancer.js';
|
|
16
17
|
|
|
17
18
|
|
|
@@ -113,7 +114,8 @@ async function main() {
|
|
|
113
114
|
subscriptionTests,
|
|
114
115
|
retentionTests,
|
|
115
116
|
maintenanceTests,
|
|
116
|
-
bootstrapTests
|
|
117
|
+
bootstrapTests,
|
|
118
|
+
loggerTests
|
|
117
119
|
]
|
|
118
120
|
|
|
119
121
|
const aiTests = [
|