azify-logger 1.0.35 → 1.0.37

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.
@@ -144,14 +144,29 @@ function createExpressLoggingMiddleware(options = {}) {
144
144
  return worker
145
145
  }
146
146
 
147
- function sendLog(level, message, meta = {}) {
148
- if (!transport || typeof transport.enqueue !== 'function') return
147
+ async function sendLog(level, message, meta = {}) {
148
+ if (!transport || typeof transport.enqueue !== 'function') {
149
+ try {
150
+ console.log(`[AZIFY-LOGGER-FALLBACK] ${message}`, JSON.stringify(meta, null, 2))
151
+ } catch (_) {
152
+ }
153
+ return
154
+ }
149
155
 
150
- transport.enqueue({
151
- level,
152
- message,
153
- meta
154
- }, { 'content-type': 'application/json' })
156
+ try {
157
+ // AGUARDAR o enqueue para garantir que o log seja adicionado ao Redis antes de continuar
158
+ // transport.enqueue agora é assíncrono e sempre retorna uma Promise
159
+ await transport.enqueue({
160
+ level,
161
+ message,
162
+ meta
163
+ }, { 'content-type': 'application/json' })
164
+ } catch (err) {
165
+ try {
166
+ console.log(`[AZIFY-LOGGER-FALLBACK] ${message}`, JSON.stringify(meta, null, 2))
167
+ } catch (_) {
168
+ }
169
+ }
155
170
  }
156
171
 
157
172
  return function azifyExpressLoggingMiddleware(req, res, next) {
@@ -210,14 +225,14 @@ function createExpressLoggingMiddleware(options = {}) {
210
225
 
211
226
  const ctx = ensureRequestContext()
212
227
 
213
- const logWithContext = (level, message, meta) => {
228
+ const logWithContext = async (level, message, meta) => {
214
229
  const otelCtx = getOtelTraceContext()
215
230
  const ctx = getRequestContext() || ensureRequestContext()
216
231
 
217
232
  meta.traceId = otelCtx?.traceId || meta.traceId || ctx.traceId
218
233
  meta.spanId = otelCtx?.spanId || meta.spanId || ctx.spanId
219
234
  meta.parentSpanId = otelCtx?.parentSpanId || meta.parentSpanId || ctx.parentSpanId
220
- sendLog(level, message, meta)
235
+ await sendLog(level, message, meta)
221
236
  }
222
237
 
223
238
  function ensureIds() {
@@ -255,19 +270,20 @@ function createExpressLoggingMiddleware(options = {}) {
255
270
  }
256
271
  }
257
272
 
258
- function emitResponseLog(meta, chunk) {
273
+ async function emitResponseLog(meta, chunk) {
259
274
  if (!config.captureResponseBody || chunk == null) {
260
- logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
275
+ await logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
261
276
  return
262
277
  }
263
278
 
264
279
  if (!meta.response) meta.response = {}
265
280
  meta.response.body = safeSerializeBody(chunk)
266
- logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
281
+ await logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
267
282
  }
268
283
 
269
284
  if (config.logRequest) {
270
- process.nextTick(() => {
285
+ let requestLogEmitted = false
286
+ try {
271
287
  ensureIds()
272
288
  ensureHeaders()
273
289
 
@@ -300,25 +316,55 @@ function createExpressLoggingMiddleware(options = {}) {
300
316
  if (config.environment) meta.environment = config.environment
301
317
 
302
318
  logWithContext('info', `[REQUEST] ${method} ${url}`, meta)
303
- })
304
- }
305
-
306
- const originalEnd = res.end.bind(res)
307
-
308
- res.end = (chunk, encoding) => {
309
- if (logSent) {
310
- return originalEnd(chunk, encoding)
319
+ requestLogEmitted = true
320
+ } catch (err) {
321
+ try {
322
+ ensureIds()
323
+ const minimalMeta = {
324
+ traceId: reqCtx?.traceId || traceId,
325
+ spanId: reqCtx?.spanId || spanId,
326
+ parentSpanId: reqCtx?.parentSpanId || parentSpanId || null,
327
+ requestId: requestId || fastUUID(),
328
+ method,
329
+ url,
330
+ path,
331
+ timestamp: Date.now(),
332
+ hostname
333
+ }
334
+ if (serviceObj) minimalMeta.service = serviceObj
335
+ if (config.environment) minimalMeta.environment = config.environment
336
+ logWithContext('info', `[REQUEST] ${method} ${url}`, minimalMeta)
337
+ requestLogEmitted = true
338
+ } catch (_) {
339
+ try {
340
+ sendLog('info', `[REQUEST] ${method} ${url}`, {
341
+ method,
342
+ url,
343
+ path
344
+ })
345
+ requestLogEmitted = true
346
+ } catch (_) {
347
+ try {
348
+ console.log(`[AZIFY-LOGGER-EMERGENCY] [REQUEST] ${method} ${url}`)
349
+ } catch (_) {
350
+ }
351
+ }
352
+ }
311
353
  }
312
- logSent = true
313
354
 
314
- const result = originalEnd(chunk, encoding)
315
-
316
- if (chunk != null && config.captureResponseBody && !responseChunkCaptured) {
317
- responseChunk = chunk
318
- responseChunkCaptured = true
355
+ if (!requestLogEmitted) {
356
+ try {
357
+ console.log(`[AZIFY-LOGGER-EMERGENCY] [REQUEST] ${method} ${url}`)
358
+ } catch (_) {
359
+ }
319
360
  }
361
+ }
362
+
363
+ async function emitResponse() {
364
+ if (logSent) return
365
+ logSent = true
320
366
 
321
- process.nextTick(() => {
367
+ try {
322
368
  ensureIds()
323
369
 
324
370
  const statusCode = res.statusCode || 200
@@ -353,12 +399,76 @@ function createExpressLoggingMiddleware(options = {}) {
353
399
  if (config.environment) meta.environment = config.environment
354
400
 
355
401
  const chunkToProcess = (responseChunk !== null && responseChunkCaptured) ? responseChunk : null
356
- emitResponseLog(meta, chunkToProcess)
357
- })
402
+ await emitResponseLog(meta, chunkToProcess)
403
+ } catch (err) {
404
+ try {
405
+ ensureIds()
406
+ const statusCode = res.statusCode || 200
407
+ const duration = Date.now() - startTime
408
+ const response = { statusCode, durationMs: duration }
409
+ const requestObj = {
410
+ id: requestId,
411
+ method,
412
+ url,
413
+ path,
414
+ ip: clientIp || 'unknown'
415
+ }
416
+ const meta = {
417
+ traceId: reqCtx?.traceId || traceId,
418
+ spanId: reqCtx?.spanId || spanId,
419
+ parentSpanId: reqCtx?.parentSpanId || parentSpanId || null,
420
+ requestId: requestId || fastUUID(),
421
+ request: requestObj,
422
+ response,
423
+ timestamp: Date.now(),
424
+ hostname
425
+ }
426
+ if (serviceObj) meta.service = serviceObj
427
+ if (config.environment) meta.environment = config.environment
428
+ await emitResponseLog(meta, null)
429
+ } catch (fallbackErr) {
430
+ try {
431
+ ensureIds()
432
+ const statusCode = res.statusCode || 200
433
+ const duration = Date.now() - startTime
434
+ await sendLog('info', `[RESPONSE] ${method} ${url}`, {
435
+ traceId: reqCtx?.traceId || traceId,
436
+ spanId: reqCtx?.spanId || spanId,
437
+ parentSpanId: reqCtx?.parentSpanId || parentSpanId || null,
438
+ requestId: requestId || fastUUID(),
439
+ method,
440
+ url,
441
+ path,
442
+ statusCode,
443
+ durationMs: duration,
444
+ timestamp: Date.now(),
445
+ hostname
446
+ })
447
+ } catch (_) {
448
+ }
449
+ }
450
+ }
451
+ }
452
+
453
+ const originalEnd = res.end.bind(res)
454
+
455
+ res.end = (chunk, encoding) => {
456
+ if (chunk != null && config.captureResponseBody && !responseChunkCaptured) {
457
+ responseChunk = chunk
458
+ responseChunkCaptured = true
459
+ }
358
460
 
461
+ const result = originalEnd(chunk, encoding)
462
+ void emitResponse()
359
463
  return result
360
464
  }
361
465
 
466
+ // Também capturar o evento 'finish' para garantir que o RESPONSE seja logado
467
+ // mesmo quando res.json() ou res.send() são usados sem chamar res.end() explicitamente
468
+ res.once('finish', () => {
469
+ void emitResponse()
470
+ })
471
+
362
472
  runWithRequestContext(ctx, () => {
363
473
  next()
364
474
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azify-logger",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
4
4
  "description": "Azify Logger Client - Centralized logging for OpenSearch",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -29,6 +29,7 @@ function createRedisProducer(config = {}) {
29
29
  const streamKey = config.streamKey || DEFAULT_STREAM_KEY
30
30
  const maxLen = Number.isFinite(config.maxLen) ? config.maxLen : DEFAULT_MAXLEN
31
31
  const redisOptions = { ...defaultRedisOptions, ...(config.redisOptions || {}) }
32
+ const spool = config.spool || null
32
33
 
33
34
  const client = new Redis(url, redisOptions)
34
35
  let lastConnectionErrorLog = 0
@@ -69,8 +70,9 @@ function createRedisProducer(config = {}) {
69
70
  const BATCH_SIZE = 1
70
71
  const BATCH_TIMEOUT = 0
71
72
  let flushing = false
73
+ let currentSpool = spool
72
74
 
73
- function flushBatch() {
75
+ async function flushBatch() {
74
76
  if (flushing || batch.length === 0) return
75
77
  flushing = true
76
78
 
@@ -80,12 +82,19 @@ function createRedisProducer(config = {}) {
80
82
  return
81
83
  }
82
84
 
83
- setImmediate(() => {
85
+ try {
84
86
  if (entriesToFlush.length === 1) {
85
87
  const entry = entriesToFlush[0]
86
88
  if (entry) {
87
- const payload = JSON.stringify(entry)
88
- client.xadd(streamKey, 'MAXLEN', '~', maxLen, '*', 'entry', payload).catch(() => {})
89
+ try {
90
+ const payload = JSON.stringify(entry)
91
+ // AGUARDAR o xadd para garantir que o log seja adicionado ao Redis antes de continuar
92
+ await client.xadd(streamKey, 'MAXLEN', '~', maxLen, '*', 'entry', payload)
93
+ } catch (err) {
94
+ if (currentSpool) {
95
+ currentSpool.append(entry).catch(() => {})
96
+ }
97
+ }
89
98
  }
90
99
  } else {
91
100
  const pipeline = client.pipeline()
@@ -94,40 +103,58 @@ function createRedisProducer(config = {}) {
94
103
  for (let i = 0; i < entriesToFlush.length; i++) {
95
104
  const entry = entriesToFlush[i]
96
105
  if (!entry) continue
97
- const payload = JSON.stringify(entry)
98
- pipeline.xadd(streamKey, 'MAXLEN', '~', maxLen, '*', 'entry', payload)
99
- validCount++
106
+ try {
107
+ const payload = JSON.stringify(entry)
108
+ pipeline.xadd(streamKey, 'MAXLEN', '~', maxLen, '*', 'entry', payload)
109
+ validCount++
110
+ } catch (err) {
111
+ if (currentSpool) {
112
+ currentSpool.append(entry).catch(() => {})
113
+ }
114
+ }
100
115
  }
101
116
 
102
117
  if (validCount > 0) {
103
- pipeline.exec().catch(() => {})
118
+ await pipeline.exec().catch((err) => {
119
+ for (const entry of entriesToFlush) {
120
+ if (currentSpool) {
121
+ currentSpool.append(entry).catch(() => {})
122
+ }
123
+ }
124
+ })
104
125
  }
105
126
  }
106
-
107
- flushing = false
108
- if (batch.length >= BATCH_SIZE) {
109
- flushBatch()
110
- } else if (batch.length > 0) {
111
- scheduleFlush()
127
+ } catch (err) {
128
+ for (const entry of entriesToFlush) {
129
+ if (currentSpool) {
130
+ currentSpool.append(entry).catch(() => {})
131
+ }
112
132
  }
113
- })
133
+ }
134
+
135
+ flushing = false
136
+ if (batch.length >= BATCH_SIZE) {
137
+ setImmediate(() => { void flushBatch() })
138
+ } else if (batch.length > 0) {
139
+ scheduleFlush()
140
+ }
114
141
  }
115
142
 
116
143
  function scheduleFlush() {
117
144
  if (batchTimer || flushing) return
118
145
 
119
146
  if (batch.length >= BATCH_SIZE) {
120
- flushBatch()
147
+ void flushBatch()
121
148
  } else if (BATCH_TIMEOUT === 0) {
122
149
  setImmediate(() => {
123
150
  if (!flushing) {
124
- flushBatch()
151
+ void flushBatch()
125
152
  }
126
153
  })
127
154
  } else {
128
155
  batchTimer = setTimeout(() => {
129
156
  batchTimer = null
130
- flushBatch()
157
+ void flushBatch()
131
158
  }, BATCH_TIMEOUT)
132
159
 
133
160
  if (typeof batchTimer.unref === 'function') {
@@ -136,7 +163,7 @@ function createRedisProducer(config = {}) {
136
163
  }
137
164
  }
138
165
 
139
- function enqueue(entry) {
166
+ async function enqueue(entry) {
140
167
  batch.push(entry)
141
168
 
142
169
  if (batch.length >= BATCH_SIZE) {
@@ -144,7 +171,8 @@ function createRedisProducer(config = {}) {
144
171
  clearTimeout(batchTimer)
145
172
  batchTimer = null
146
173
  }
147
- flushBatch()
174
+ // AGUARDAR o flushBatch quando o batch está cheio para garantir que o log seja adicionado ao Redis
175
+ await flushBatch()
148
176
  } else if (batch.length === 1) {
149
177
  scheduleFlush()
150
178
  }
@@ -156,7 +184,7 @@ function createRedisProducer(config = {}) {
156
184
  batchTimer = null
157
185
  }
158
186
  while (batch.length > 0) {
159
- flushBatch()
187
+ await flushBatch()
160
188
  await new Promise(resolve => setTimeout(resolve, 10))
161
189
  }
162
190
  await client.quit().catch(() => {})
@@ -167,7 +195,11 @@ function createRedisProducer(config = {}) {
167
195
  client,
168
196
  streamKey,
169
197
  close,
170
- flush: flushBatch
198
+ flush: flushBatch,
199
+ setSpool(newSpool) {
200
+ currentSpool = newSpool
201
+ },
202
+ _spool: null
171
203
  }
172
204
  }
173
205