azify-logger 1.0.28 → 1.0.29

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/README.md CHANGED
@@ -49,7 +49,21 @@ APP_NAME=nome-app
49
49
  require('azify-logger')
50
50
  const express = require('express')
51
51
  const app = express()
52
- // Logs automáticos via OpenTelemetry
52
+ const azifyMiddleware = require('azify-logger/middleware-express')
53
+ app.use(azifyMiddleware())
54
+ ```
55
+
56
+ ### Para aplicações Fastify:
57
+
58
+ ```javascript
59
+ const fastify = require('fastify')()
60
+ const azifyPlugin = require('azify-logger/middleware-fastify')
61
+
62
+ await fastify.register(azifyPlugin, {
63
+ serviceName: 'minha-app'
64
+ })
65
+
66
+ await fastify.listen({ port: 3000 })
53
67
  ```
54
68
 
55
69
  ## ⚙️ Variáveis de Ambiente
@@ -192,11 +206,11 @@ O deploy é feito automaticamente via GitHub Actions.
192
206
 
193
207
  **Comandos seguros** (preservam dados):
194
208
  ```bash
195
- docker-compose stop
196
- docker-compose restart
209
+ docker compose stop
210
+ docker compose restart
197
211
  ```
198
212
 
199
213
  **Comandos destrutivos** (APAGAM logs):
200
214
  ```bash
201
- docker-compose down -v # ⚠️ APAGA VOLUMES!
215
+ docker compose down -v # ⚠️ APAGA VOLUMES!
202
216
  ```
package/index.js CHANGED
@@ -341,7 +341,8 @@ module.exports.streams = {
341
341
  }
342
342
  module.exports.middleware = {
343
343
  restify: require('./middleware-restify'),
344
- express: require('./middleware-express')
344
+ express: require('./middleware-express'),
345
+ fastify: require('./middleware-fastify')
345
346
  }
346
347
  module.exports.startLoggerWorker = require('./queue/workerManager').startLoggerWorker
347
348
  module.exports.stopLoggerWorker = require('./queue/workerManager').stopLoggerWorker
@@ -1,8 +1,52 @@
1
- const { runWithRequestContext, startRequestContext, getRequestContext } = require('./store')
1
+ const { startRequestContext } = require('./store')
2
2
  const { createHttpLoggerTransport } = require('./streams/httpQueue')
3
- const { randomUUID } = require('crypto')
3
+ const { Worker } = require('worker_threads')
4
+ const path = require('path')
4
5
  const os = require('os')
5
6
 
7
+ function fastUUID() {
8
+ const timestamp = Date.now().toString(36)
9
+ const randomPart = Math.random().toString(36).substring(2, 15)
10
+ const randomPart2 = Math.random().toString(36).substring(2, 15)
11
+ return `${timestamp}-${randomPart}-${randomPart2}`.substring(0, 36)
12
+ }
13
+
14
+ function sanitizeTraceHex(value) {
15
+ if (!value || typeof value !== 'string') return null
16
+ const hex = value.replace(/[^0-9a-fA-F]/g, '').toLowerCase()
17
+ if (!hex) return null
18
+ return hex.padEnd(32, '0').slice(0, 32)
19
+ }
20
+
21
+ function safeSerializeBody(payload) {
22
+ if (payload == null) return ''
23
+ if (typeof payload === 'string') return payload
24
+ if (Buffer.isBuffer(payload)) return payload.toString('utf8')
25
+ try {
26
+ return JSON.stringify(payload)
27
+ } catch (err) {
28
+ try {
29
+ return String(payload)
30
+ } catch {
31
+ return ''
32
+ }
33
+ }
34
+ }
35
+
36
+ function normalizeChunkToBuffer(chunk) {
37
+ if (chunk == null) return null
38
+ if (Buffer.isBuffer(chunk)) return chunk
39
+ if (typeof chunk === 'string') return Buffer.from(chunk)
40
+ if (chunk && chunk.type === 'Buffer' && Array.isArray(chunk.data)) {
41
+ return Buffer.from(chunk.data)
42
+ }
43
+ try {
44
+ return Buffer.from(String(chunk))
45
+ } catch {
46
+ return null
47
+ }
48
+ }
49
+
6
50
  const HEADER_WHITELIST = new Set([
7
51
  'content-type',
8
52
  'content-length',
@@ -52,27 +96,52 @@ function createExpressLoggingMiddleware(options = {}) {
52
96
 
53
97
  const hostname = os.hostname()
54
98
 
99
+ const serviceObj = config.serviceName ? { name: config.serviceName, version: '1.0.0' } : null
100
+
101
+ const workerPool = []
102
+ const cpuCount = Math.max(1, (os.cpus() || []).length || 1)
103
+ const WORKER_POOL_SIZE = Math.min(8, Math.max(2, cpuCount))
104
+ const maxWorkers = Math.min(WORKER_POOL_SIZE, cpuCount)
105
+ let workerIndex = 0
106
+ let poolInitialized = false
107
+
108
+ function initWorkerPool() {
109
+ try {
110
+ const workerPath = path.join(__dirname, 'utils', 'bodyWorker.js')
111
+ for (let i = 0; i < maxWorkers; i++) {
112
+ try {
113
+ const worker = new Worker(workerPath)
114
+ workerPool.push(worker)
115
+ } catch (err) {
116
+ break
117
+ }
118
+ }
119
+ } catch (err) {
120
+ }
121
+ }
122
+
123
+ function ensureWorkerPool() {
124
+ if (!poolInitialized && (config.captureResponseBody || config.captureRequestBody)) {
125
+ poolInitialized = true
126
+ initWorkerPool()
127
+ }
128
+ }
129
+
130
+ function getWorker() {
131
+ if (workerPool.length === 0) return null
132
+ const worker = workerPool[workerIndex % workerPool.length]
133
+ workerIndex++
134
+ return worker
135
+ }
136
+
55
137
  function sendLog(level, message, meta = {}) {
56
138
  if (!transport || typeof transport.enqueue !== 'function') return
57
139
 
58
- try {
59
- const metaObj = {
60
- service: {
61
- name: config.serviceName,
62
- version: '1.0.0'
63
- },
64
- environment: config.environment,
65
- timestamp: new Date().toISOString(),
66
- hostname: hostname,
67
- ...meta
68
- }
69
-
70
- transport.enqueue({
71
- level,
72
- message,
73
- meta: metaObj
74
- }, { 'content-type': 'application/json' })
75
- } catch (err) { }
140
+ transport.enqueue({
141
+ level,
142
+ message,
143
+ meta
144
+ }, { 'content-type': 'application/json' })
76
145
  }
77
146
 
78
147
  return function azifyExpressLoggingMiddleware(req, res, next) {
@@ -85,181 +154,162 @@ function createExpressLoggingMiddleware(options = {}) {
85
154
  let responseChunkCaptured = false
86
155
  let logSent = false
87
156
 
88
- let requestId, traceId, spanId, parentSpanId, reqCtx, clientIp, query, cachedHeaders
157
+ let requestId, traceId, spanId, parentSpanId, clientIp, query, cachedHeaders
89
158
  let idsCreated = false
90
159
  let headersCached = false
160
+ let reqCtx = null
161
+
162
+ next()
163
+
164
+ const logWithContext = (level, message, meta) => {
165
+ if (!reqCtx) {
166
+ const traceHex = sanitizeTraceHex(req.headers['x-trace-id'])
167
+ reqCtx = startRequestContext({
168
+ requestId: requestId || req.requestId || fastUUID(),
169
+ traceHex,
170
+ parentSpanId: req.headers['x-parent-span-id'] || null
171
+ })
172
+ }
173
+ meta.traceId = meta.traceId || reqCtx.traceId
174
+ meta.spanId = meta.spanId || reqCtx.spanId
175
+ meta.parentSpanId = meta.parentSpanId || reqCtx.parentSpanId
176
+ sendLog(level, message, meta)
177
+ }
91
178
 
92
179
  function ensureIds() {
93
180
  if (idsCreated) return
94
181
  idsCreated = true
95
182
 
96
- query = req.query
183
+ query = req.query || null
97
184
 
98
- const currentCtx = getRequestContext()
99
- const headerTraceId = req.headers['x-trace-id']
100
- const headerParentSpanId = req.headers['x-parent-span-id']
101
-
102
- if (currentCtx?.traceId) {
103
- traceId = currentCtx.traceId
104
- } else if (headerTraceId) {
105
- traceId = headerTraceId
106
- } else {
107
- traceId = randomUUID()
185
+ if (!reqCtx) {
186
+ const traceHex = sanitizeTraceHex(req.headers['x-trace-id'])
187
+ reqCtx = startRequestContext({
188
+ requestId: req.requestId || fastUUID(),
189
+ traceHex,
190
+ parentSpanId: req.headers['x-parent-span-id'] || null
191
+ })
108
192
  }
109
193
 
110
- parentSpanId = currentCtx?.spanId || headerParentSpanId || null
111
-
112
- if (!req.requestId) {
113
- const singleUUID = randomUUID()
114
- requestId = singleUUID
115
- spanId = singleUUID.replace(/-/g, '').substring(0, 16)
116
- } else {
117
- requestId = req.requestId
118
- if (requestId.length >= 16) {
119
- spanId = requestId.replace(/-/g, '').substring(0, 16)
120
- } else if (traceId.length >= 16) {
121
- spanId = traceId.replace(/-/g, '').substring(0, 16)
122
- } else {
123
- spanId = randomUUID().replace(/-/g, '').substring(0, 16)
124
- }
125
- }
194
+ requestId = reqCtx.requestId || req.requestId || fastUUID()
195
+ traceId = reqCtx.traceId
196
+ spanId = reqCtx.spanId
197
+ parentSpanId = reqCtx.parentSpanId || req.headers['x-parent-span-id'] || null
126
198
 
127
199
  clientIp = req.ip || req.connection?.remoteAddress || req.socket?.remoteAddress || 'unknown'
128
-
129
- reqCtx = startRequestContext({ requestId, traceId, spanId, parentSpanId })
200
+ req.requestId = requestId
130
201
  }
131
202
 
132
203
  function ensureHeaders() {
133
204
  if (headersCached) return
134
205
  headersCached = true
135
- cachedHeaders = config.captureHeaders ? pickHeaders(req.headers || {}) : {}
206
+ if (config.captureHeaders) {
207
+ cachedHeaders = pickHeaders(req.headers || {})
208
+ } else {
209
+ cachedHeaders = {}
210
+ }
136
211
  }
137
212
 
213
+ function emitResponseLog(meta, chunk) {
214
+ if (!config.captureResponseBody || chunk == null) {
215
+ logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
216
+ return
217
+ }
218
+
219
+ if (!meta.response) meta.response = {}
220
+ meta.response.body = safeSerializeBody(chunk)
221
+ logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
222
+ }
223
+
138
224
  if (config.logRequest) {
139
- setImmediate(() => {
140
- try {
141
- ensureIds()
142
- ensureHeaders()
143
- const request = {
144
- id: requestId,
145
- method,
146
- url,
147
- path,
148
- query: query || {},
149
- headers: cachedHeaders,
150
- ip: clientIp
151
- }
152
- if (req.body && config.captureRequestBody) {
153
- request.body = req.body
154
- }
155
- sendLog('info', `[REQUEST] ${method} ${url}`, {
156
- traceId: reqCtx.traceId,
157
- spanId: reqCtx.spanId,
158
- parentSpanId: reqCtx.parentSpanId,
159
- requestId: requestId,
160
- request: request
161
- })
162
- } catch (err) { }
225
+ process.nextTick(() => {
226
+ ensureIds()
227
+ ensureHeaders()
228
+
229
+ const request = {
230
+ id: requestId,
231
+ method,
232
+ url,
233
+ path,
234
+ ip: clientIp
235
+ }
236
+ if (query) request.query = query
237
+ if (config.captureHeaders && cachedHeaders) request.headers = cachedHeaders
238
+ if (config.captureRequestBody && req.body !== undefined && req.body != null) {
239
+ request.body = safeSerializeBody(req.body)
240
+ }
241
+
242
+ const meta = {
243
+ traceId: reqCtx.traceId,
244
+ spanId: reqCtx.spanId,
245
+ parentSpanId: reqCtx.parentSpanId || null,
246
+ requestId,
247
+ request,
248
+ timestamp: Date.now(),
249
+ hostname
250
+ }
251
+ if (serviceObj) meta.service = serviceObj
252
+ if (config.environment) meta.environment = config.environment
253
+
254
+ logWithContext('info', `[REQUEST] ${method} ${url}`, meta)
163
255
  })
164
256
  }
165
-
257
+
166
258
  const originalEnd = res.end.bind(res)
167
259
 
168
260
  res.end = (chunk, encoding) => {
261
+ if (logSent) {
262
+ return originalEnd(chunk, encoding)
263
+ }
264
+ logSent = true
265
+
169
266
  const result = originalEnd(chunk, encoding)
170
267
 
171
- setImmediate(() => {
172
- if (logSent) return
173
- logSent = true
268
+ if (chunk != null && config.captureResponseBody && !responseChunkCaptured) {
269
+ responseChunk = chunk
270
+ responseChunkCaptured = true
271
+ }
272
+
273
+ process.nextTick(() => {
274
+ ensureIds()
174
275
 
175
- try {
176
- if (chunk != null && config.captureResponseBody && !responseChunkCaptured) {
177
- responseChunk = chunk
178
- responseChunkCaptured = true
179
- }
180
-
181
- ensureIds()
182
-
183
- const statusCode = res.statusCode || 200
184
- const duration = Date.now() - startTime
185
-
186
- const response = { statusCode, durationMs: duration }
187
- if (responseChunk !== null && responseChunkCaptured && config.captureResponseBody) {
188
- response.body = responseChunk
189
- }
190
-
191
- if (config.captureHeaders) {
192
- ensureHeaders()
193
- }
194
-
195
- const meta = {
196
- traceId: reqCtx.traceId,
197
- spanId: reqCtx.spanId,
198
- parentSpanId: reqCtx.parentSpanId,
199
- requestId: requestId,
200
- request: {
201
- id: requestId,
202
- method,
203
- url,
204
- path,
205
- query: query || {},
206
- headers: config.captureHeaders ? (cachedHeaders || {}) : {},
207
- ip: clientIp
208
- },
209
- response: response
210
- }
211
-
212
- sendLog('info', `[RESPONSE] ${method} ${url}`, meta)
213
- } catch (err) { }
276
+ const statusCode = res.statusCode || 200
277
+ const duration = Date.now() - startTime
278
+ const response = { statusCode, durationMs: duration }
279
+
280
+ if (config.captureHeaders) {
281
+ ensureHeaders()
282
+ }
283
+
284
+ const requestObj = {
285
+ id: requestId,
286
+ method,
287
+ url,
288
+ path,
289
+ ip: clientIp
290
+ }
291
+ if (query) requestObj.query = query
292
+ if (config.captureHeaders && cachedHeaders) requestObj.headers = cachedHeaders
293
+
294
+ const meta = {
295
+ traceId: reqCtx.traceId,
296
+ spanId: reqCtx.spanId,
297
+ parentSpanId: reqCtx.parentSpanId || null,
298
+ requestId,
299
+ request: requestObj,
300
+ response,
301
+ timestamp: Date.now(),
302
+ hostname
303
+ }
304
+ if (serviceObj) meta.service = serviceObj
305
+ if (config.environment) meta.environment = config.environment
306
+
307
+ const chunkToProcess = (responseChunk !== null && responseChunkCaptured) ? responseChunk : null
308
+ emitResponseLog(meta, chunkToProcess)
214
309
  })
215
310
 
216
311
  return result
217
312
  }
218
-
219
- try {
220
- next()
221
- } catch (err) {
222
- if (!logSent) {
223
- logSent = true
224
- ensureIds()
225
- runWithRequestContext(reqCtx, () => {
226
- process.stderr.write(`[azify-logger][middleware] Erro no next(): ${err?.message || String(err)}\n`)
227
- setImmediate(() => {
228
- try {
229
- const statusCode = res.statusCode || 500
230
- const duration = Date.now() - startTime
231
-
232
- ensureHeaders()
233
-
234
- const response = { statusCode, durationMs: duration }
235
- const meta = {
236
- traceId: reqCtx.traceId,
237
- spanId: reqCtx.spanId,
238
- parentSpanId: reqCtx.parentSpanId,
239
- requestId: requestId,
240
- request: {
241
- id: requestId,
242
- method,
243
- url,
244
- path,
245
- query: query || {},
246
- headers: cachedHeaders,
247
- ip: clientIp
248
- },
249
- response: response,
250
- error: {
251
- message: err?.message || String(err),
252
- name: err?.name || 'Error',
253
- stack: err?.stack
254
- }
255
- }
256
- sendLog('error', `[RESPONSE] ${method} ${url}`, meta)
257
- } catch (logErr) { }
258
- })
259
- })
260
- }
261
- throw err
262
- }
263
313
  }
264
314
  }
265
315