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 +18 -4
- package/index.js +2 -1
- package/middleware-express.js +213 -163
- package/middleware-fastify.js +348 -0
- package/middleware-restify.js +74 -59
- package/package.json +16 -17
- package/queue/redisQueue.js +42 -44
- package/sampling.js +1 -1
- package/scripts/redis-worker.js +25 -53
- package/server.js +44 -47
- package/store.js +10 -4
- package/streams/httpQueue.js +65 -80
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
|
-
|
|
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
|
|
196
|
-
docker
|
|
209
|
+
docker compose stop
|
|
210
|
+
docker compose restart
|
|
197
211
|
```
|
|
198
212
|
|
|
199
213
|
**Comandos destrutivos** (APAGAM logs):
|
|
200
214
|
```bash
|
|
201
|
-
docker
|
|
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
|
package/middleware-express.js
CHANGED
|
@@ -1,8 +1,52 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { startRequestContext } = require('./store')
|
|
2
2
|
const { createHttpLoggerTransport } = require('./streams/httpQueue')
|
|
3
|
-
const {
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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,
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
268
|
+
if (chunk != null && config.captureResponseBody && !responseChunkCaptured) {
|
|
269
|
+
responseChunk = chunk
|
|
270
|
+
responseChunkCaptured = true
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
process.nextTick(() => {
|
|
274
|
+
ensureIds()
|
|
174
275
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|