azify-logger 1.0.54 → 1.0.55-test-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.
- package/package.json +2 -1
- package/register-http-client-early.js +204 -50
- package/register.js +213 -63
- package/scripts/redis-worker.js +3 -0
- package/server.js +21 -9
- package/utils/undiciLogBodies.js +83 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azify-logger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.55-test-1",
|
|
4
4
|
"description": "Azify Logger Client - Centralized logging for OpenSearch",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -111,6 +111,7 @@
|
|
|
111
111
|
"queue/",
|
|
112
112
|
"scripts/redis-worker.js",
|
|
113
113
|
"streams/",
|
|
114
|
+
"utils/undiciLogBodies.js",
|
|
114
115
|
"package.json",
|
|
115
116
|
"README.md"
|
|
116
117
|
]
|
|
@@ -11,6 +11,11 @@ try {
|
|
|
11
11
|
const Module = require('module')
|
|
12
12
|
const path = require('path')
|
|
13
13
|
const EARLY_DIR = __dirname
|
|
14
|
+
const {
|
|
15
|
+
resolveUndiciRequestOpts,
|
|
16
|
+
serializeUndiciRequestBody,
|
|
17
|
+
restoreUndiciResponseBodyAfterReadForLog
|
|
18
|
+
} = require(path.join(EARLY_DIR, 'utils/undiciLogBodies'))
|
|
14
19
|
|
|
15
20
|
function getOtelTraceContext() {
|
|
16
21
|
try {
|
|
@@ -28,13 +33,70 @@ try {
|
|
|
28
33
|
const MAX_EARLY_LOG_QUEUE = 2000
|
|
29
34
|
const earlyLogQueue = []
|
|
30
35
|
|
|
36
|
+
function normalizePathEarly(p) {
|
|
37
|
+
if (!p) return '/'
|
|
38
|
+
const trimmed = String(p).replace(/\/+$/, '')
|
|
39
|
+
return trimmed === '' ? '/' : trimmed
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Ignora esquema (http vs https): undici muitas vezes resolve como https e AZIFY_LOGGER_URL é http em dev. */
|
|
31
43
|
function isLoggerUrl(urlStr) {
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
const loggerUrlRaw = process.env.AZIFY_LOGGER_URL || 'http://localhost:3001/log'
|
|
45
|
+
if (typeof urlStr !== 'string' || !urlStr.trim()) return false
|
|
46
|
+
try {
|
|
47
|
+
const base = new URL(String(loggerUrlRaw).trim())
|
|
48
|
+
const cand = new URL(String(urlStr).trim())
|
|
49
|
+
if (base.hostname.toLowerCase() !== cand.hostname.toLowerCase()) return false
|
|
50
|
+
const portBase = base.port || (base.protocol === 'https:' ? '443' : '80')
|
|
51
|
+
const portCand = cand.port || (cand.protocol === 'https:' ? '443' : '80')
|
|
52
|
+
if (String(portBase) !== String(portCand)) return false
|
|
53
|
+
const lp = normalizePathEarly(base.pathname)
|
|
54
|
+
const tp = normalizePathEarly(cand.pathname)
|
|
55
|
+
return tp === lp || tp.startsWith(lp + '/')
|
|
56
|
+
} catch (_) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** undici.request(url, opts) / request(opts) — muitas chamadas não trazem origin; path pode ser URL absoluta ou só path + hostname. */
|
|
62
|
+
function resolveUrlStrFromUndiciRequestArgs(url, options) {
|
|
63
|
+
const opts = (typeof url === 'object' && url !== null && !(url instanceof URL)) ? url : options
|
|
64
|
+
const urlArg = (typeof url === 'string' || (url && (url.href || url instanceof URL))) ? url : (opts && (opts.url || opts.uri))
|
|
65
|
+
if (typeof urlArg === 'string') return urlArg
|
|
66
|
+
if (urlArg && typeof urlArg.href === 'string') return urlArg.href
|
|
67
|
+
if (urlArg && typeof urlArg.toString === 'function') return urlArg.toString()
|
|
68
|
+
if (opts && typeof opts.origin === 'string' && opts.path != null) {
|
|
69
|
+
return opts.origin + (String(opts.path).startsWith('/') ? '' : '/') + String(opts.path)
|
|
70
|
+
}
|
|
71
|
+
if (opts && typeof opts.path === 'string' && /^https?:\/\//i.test(opts.path)) {
|
|
72
|
+
return opts.path
|
|
73
|
+
}
|
|
74
|
+
if (opts && (opts.hostname || opts.host)) {
|
|
75
|
+
const h = String(opts.hostname || String(opts.host || '').split(':')[0]).trim()
|
|
76
|
+
if (h) {
|
|
77
|
+
const hl = h.toLowerCase()
|
|
78
|
+
const isLocal = hl === 'localhost' || hl === '127.0.0.1' || hl === '::1'
|
|
79
|
+
const proto = (opts.protocol && String(opts.protocol).replace(/:$/, '')) || (isLocal ? 'http' : 'https')
|
|
80
|
+
const pnum = opts.port != null && opts.port !== '' ? Number(opts.port) : NaN
|
|
81
|
+
const port = !Number.isNaN(pnum) && pnum !== 80 && pnum !== 443 ? ':' + pnum : ''
|
|
82
|
+
const p = opts.path != null ? String(opts.path) : '/'
|
|
83
|
+
return proto + '://' + h + port + (p.startsWith('/') ? p : '/' + p)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return 'unknown'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveUrlStrFromDispatcherOpts(opts) {
|
|
90
|
+
if (!opts || typeof opts !== 'object') return 'unknown'
|
|
91
|
+
const origin = (opts.origin) || ''
|
|
92
|
+
const pathPart = opts.path != null ? opts.path : '/'
|
|
93
|
+
const pathStr = String(pathPart)
|
|
94
|
+
if (/^https?:\/\//i.test(pathStr)) return pathStr
|
|
95
|
+
if (origin) return origin + (pathStr.startsWith('/') ? pathStr : '/' + pathStr)
|
|
96
|
+
if (opts.hostname || opts.host) {
|
|
97
|
+
return resolveUrlStrFromUndiciRequestArgs(null, opts)
|
|
98
|
+
}
|
|
99
|
+
return 'unknown'
|
|
38
100
|
}
|
|
39
101
|
|
|
40
102
|
function send(level, message, meta) {
|
|
@@ -98,8 +160,19 @@ try {
|
|
|
98
160
|
}
|
|
99
161
|
|
|
100
162
|
function patchUndiciExports(exports) {
|
|
163
|
+
if (!exports || typeof exports !== 'object') return
|
|
164
|
+
if (typeof exports.fetch === 'function' && typeof globalThis.fetch === 'function' && globalThis.fetch.__azifyLoggerFetchPatched) {
|
|
165
|
+
if (exports.fetch !== globalThis.fetch) {
|
|
166
|
+
exports.fetch = globalThis.fetch
|
|
167
|
+
httpLog('undici exports.fetch -> globalThis.fetch (body logging)')
|
|
168
|
+
}
|
|
169
|
+
}
|
|
101
170
|
const hasRequest = exports && typeof exports.request === 'function' && !exports.request.__azifyPatched
|
|
102
|
-
const hasFetch =
|
|
171
|
+
const hasFetch =
|
|
172
|
+
exports &&
|
|
173
|
+
typeof exports.fetch === 'function' &&
|
|
174
|
+
!exports.fetch.__azifyPatched &&
|
|
175
|
+
!exports.fetch.__azifyLoggerFetchPatched
|
|
103
176
|
if (!exports || (!hasRequest && !hasFetch)) return
|
|
104
177
|
const deps = loadStoreAndSampling()
|
|
105
178
|
const noDeps = !deps
|
|
@@ -118,12 +191,7 @@ try {
|
|
|
118
191
|
exports.request = function (url, options, callback) {
|
|
119
192
|
const opts = (typeof url === 'object' && url !== null && !(url instanceof URL)) ? url : options
|
|
120
193
|
const method = ((opts && opts.method) || 'GET').toUpperCase()
|
|
121
|
-
|
|
122
|
-
const urlArg = (typeof url === 'string' || (url && (url.href || url instanceof URL))) ? url : (opts && (opts.url || opts.uri))
|
|
123
|
-
if (typeof urlArg === 'string') urlStr = urlArg
|
|
124
|
-
else if (urlArg && typeof urlArg.href === 'string') urlStr = urlArg.href
|
|
125
|
-
else if (urlArg && typeof urlArg.toString === 'function') urlStr = urlArg.toString()
|
|
126
|
-
else if (opts && typeof opts.origin === 'string' && opts.path != null) urlStr = opts.origin + (String(opts.path).startsWith('/') ? '' : '/') + String(opts.path)
|
|
194
|
+
const urlStr = resolveUrlStrFromUndiciRequestArgs(url, options)
|
|
127
195
|
if (process.env.AZIFY_LOGGER_DEBUG === '1') { try { process.stderr.write('[AZIFY-PATCH] undici.request ' + method + ' ' + urlStr.slice(0, 80) + '\n') } catch (_) {} }
|
|
128
196
|
const _ep = process.env.AZIFY_DEBUG_LOG_PATH
|
|
129
197
|
if (_ep) { try { require('fs').appendFileSync(_ep, new Date().toISOString() + ' [AZIFY] WRAPPER exports.request ' + method + ' ' + urlStr.slice(0, 100) + '\n') } catch (_) {} }
|
|
@@ -136,26 +204,50 @@ try {
|
|
|
136
204
|
const traceCtx = ctx || otelCtx
|
|
137
205
|
const rawTraceId = traceCtx && traceCtx.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
138
206
|
const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
|
|
207
|
+
const reqOpts = resolveUndiciRequestOpts(url, options)
|
|
208
|
+
const requestBodyStr = serializeUndiciRequestBody(reqOpts)
|
|
139
209
|
const requestMeta = { traceId, spanId: (traceCtx && traceCtx.spanId) || null, parentSpanId: (traceCtx && traceCtx.parentSpanId) || null, requestId: (traceCtx && traceCtx.requestId) || null, method, url: urlStr }
|
|
210
|
+
if (requestBodyStr != null) requestMeta.requestBody = requestBodyStr
|
|
140
211
|
markSource(requestMeta, 'http-client')
|
|
141
212
|
const start = require('perf_hooks').performance.now()
|
|
142
213
|
const skipLog = isLoggerUrl(urlStr)
|
|
143
214
|
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) send('info', `[REQUEST] ${method} ${urlStr}`, requestMeta)
|
|
144
215
|
const wrappedCb = callback ? function (err, data) {
|
|
145
216
|
const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
|
|
146
|
-
if (
|
|
147
|
-
if (
|
|
148
|
-
|
|
217
|
+
if (err) {
|
|
218
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
219
|
+
send('error', `[RESPONSE] ${method} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
|
|
220
|
+
}
|
|
221
|
+
return callback(err, data)
|
|
149
222
|
}
|
|
150
|
-
return callback(err, data)
|
|
223
|
+
if (!data) return callback(err, data)
|
|
224
|
+
restoreUndiciResponseBodyAfterReadForLog(data).then(({ logStr, data: restored }) => {
|
|
225
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
226
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
227
|
+
if (logStr != null) meta.responseBody = logStr
|
|
228
|
+
send('info', `[RESPONSE] ${method} ${urlStr} ${restored.statusCode} ${duration}ms`, meta)
|
|
229
|
+
}
|
|
230
|
+
return callback(null, restored)
|
|
231
|
+
}).catch(() => {
|
|
232
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
233
|
+
send('info', `[RESPONSE] ${method} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
|
|
234
|
+
}
|
|
235
|
+
return callback(null, data)
|
|
236
|
+
})
|
|
151
237
|
} : undefined
|
|
152
238
|
if (wrappedCb) return origRequest.call(this, url, options, wrappedCb)
|
|
153
239
|
const p = origRequest.call(this, url, options)
|
|
154
240
|
if (p && typeof p.then === 'function') {
|
|
155
241
|
return p.then(
|
|
156
|
-
(data) => {
|
|
242
|
+
async (data) => {
|
|
157
243
|
const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
|
|
158
|
-
if (HTTP_CLIENT_MODE !== 'off' && !skipLog && data)
|
|
244
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog && data) {
|
|
245
|
+
const { logStr, data: restored } = await restoreUndiciResponseBodyAfterReadForLog(data)
|
|
246
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
247
|
+
if (logStr != null) meta.responseBody = logStr
|
|
248
|
+
send('info', `[RESPONSE] ${method} ${urlStr} ${restored.statusCode} ${duration}ms`, meta)
|
|
249
|
+
return restored
|
|
250
|
+
}
|
|
159
251
|
return data
|
|
160
252
|
},
|
|
161
253
|
(err) => {
|
|
@@ -174,34 +266,55 @@ try {
|
|
|
174
266
|
const origDisp = proto.request
|
|
175
267
|
proto.request = function (opts, callback) {
|
|
176
268
|
const methodStr = String((opts && opts.method) || 'GET').toUpperCase()
|
|
177
|
-
const
|
|
178
|
-
const path = (opts && opts.path) != null ? opts.path : '/'
|
|
179
|
-
const urlStr = origin ? (origin + (path.startsWith('/') ? path : '/' + path)) : 'unknown'
|
|
269
|
+
const urlStr = resolveUrlStrFromDispatcherOpts(opts)
|
|
180
270
|
httpLog('WRAPPER Dispatcher.request ' + methodStr + ' ' + urlStr.slice(0, 80))
|
|
181
271
|
const ctx = getRequestContext() || getLastJobContext()
|
|
182
272
|
const otelCtx = getOtelTraceContext()
|
|
183
273
|
const traceCtx = ctx || otelCtx
|
|
184
274
|
const rawTraceId = (traceCtx && traceCtx.traceId) || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
185
275
|
const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
|
|
276
|
+
const requestBodyStr = serializeUndiciRequestBody(opts || {})
|
|
186
277
|
const requestMeta = { traceId, spanId: (traceCtx && traceCtx.spanId) || null, parentSpanId: (traceCtx && traceCtx.parentSpanId) || null, requestId: (traceCtx && traceCtx.requestId) || null, method: methodStr, url: urlStr }
|
|
278
|
+
if (requestBodyStr != null) requestMeta.requestBody = requestBodyStr
|
|
187
279
|
markSource(requestMeta, 'http-client')
|
|
188
280
|
const start = require('perf_hooks').performance.now()
|
|
189
281
|
const skipLog = isLoggerUrl(urlStr)
|
|
190
282
|
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) send('info', `[REQUEST] ${methodStr} ${urlStr}`, requestMeta)
|
|
191
283
|
const wrappedCb = typeof callback === 'function' ? function (err, data) {
|
|
192
284
|
const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
|
|
193
|
-
if (
|
|
194
|
-
if (
|
|
195
|
-
|
|
285
|
+
if (err) {
|
|
286
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
287
|
+
send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
|
|
288
|
+
}
|
|
289
|
+
return callback(err, data)
|
|
196
290
|
}
|
|
197
|
-
return callback(err, data)
|
|
291
|
+
if (!data) return callback(err, data)
|
|
292
|
+
restoreUndiciResponseBodyAfterReadForLog(data).then(({ logStr, data: restored }) => {
|
|
293
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
294
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
295
|
+
if (logStr != null) meta.responseBody = logStr
|
|
296
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${restored.statusCode} ${duration}ms`, meta)
|
|
297
|
+
}
|
|
298
|
+
return callback(null, restored)
|
|
299
|
+
}).catch(() => {
|
|
300
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
301
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
|
|
302
|
+
}
|
|
303
|
+
return callback(null, data)
|
|
304
|
+
})
|
|
198
305
|
} : undefined
|
|
199
306
|
const ret = wrappedCb ? origDisp.call(this, opts, wrappedCb) : origDisp.call(this, opts)
|
|
200
307
|
if (ret && typeof ret.then === 'function' && !wrappedCb) {
|
|
201
308
|
return ret.then(
|
|
202
|
-
(data) => {
|
|
309
|
+
async (data) => {
|
|
203
310
|
const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
|
|
204
|
-
if (HTTP_CLIENT_MODE !== 'off' && !skipLog && data)
|
|
311
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog && data) {
|
|
312
|
+
const { logStr, data: restored } = await restoreUndiciResponseBodyAfterReadForLog(data)
|
|
313
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
314
|
+
if (logStr != null) meta.responseBody = logStr
|
|
315
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${restored.statusCode} ${duration}ms`, meta)
|
|
316
|
+
return restored
|
|
317
|
+
}
|
|
205
318
|
return data
|
|
206
319
|
},
|
|
207
320
|
(err) => {
|
|
@@ -255,34 +368,55 @@ try {
|
|
|
255
368
|
if (typeof origReq !== 'function') return d
|
|
256
369
|
d.request = function (opts, callback) {
|
|
257
370
|
const methodStr = String((opts && opts.method) || 'GET').toUpperCase()
|
|
258
|
-
const
|
|
259
|
-
const pathPart = (opts && opts.path) != null ? opts.path : '/'
|
|
260
|
-
const urlStr = origin ? (origin + (pathPart.startsWith('/') ? pathPart : '/' + pathPart)) : 'unknown'
|
|
371
|
+
const urlStr = resolveUrlStrFromDispatcherOpts(opts)
|
|
261
372
|
httpLog('WRAPPER getGlobalDispatcher().request ' + methodStr + ' ' + urlStr.slice(0, 80))
|
|
262
373
|
const ctx = getRequestContext ? getRequestContext() : (getLastJobContext ? getLastJobContext() : null)
|
|
263
374
|
const otelCtx = getOtelTraceContext()
|
|
264
375
|
const traceCtx = ctx || otelCtx
|
|
265
376
|
const rawTraceId = (traceCtx && traceCtx.traceId) || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
266
377
|
const traceId = typeof rawTraceId === 'string' ? (toTraceIdHex ? toTraceIdHex(rawTraceId) : rawTraceId) : rawTraceId
|
|
378
|
+
const requestBodyStr = serializeUndiciRequestBody(opts || {})
|
|
267
379
|
const requestMeta = { traceId, spanId: (traceCtx && traceCtx.spanId) || null, parentSpanId: (traceCtx && traceCtx.parentSpanId) || null, requestId: (traceCtx && traceCtx.requestId) || null, method: methodStr, url: urlStr }
|
|
380
|
+
if (requestBodyStr != null) requestMeta.requestBody = requestBodyStr
|
|
268
381
|
if (markSource) markSource(requestMeta, 'http-client')
|
|
269
382
|
const start = require('perf_hooks').performance.now()
|
|
270
383
|
const skipLog = isLoggerUrl(urlStr)
|
|
271
384
|
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) send('info', `[REQUEST] ${methodStr} ${urlStr}`, requestMeta)
|
|
272
385
|
const wrappedCb = typeof callback === 'function' ? function (err, data) {
|
|
273
386
|
const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
|
|
274
|
-
if (
|
|
275
|
-
if (
|
|
276
|
-
|
|
387
|
+
if (err) {
|
|
388
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
389
|
+
send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
|
|
390
|
+
}
|
|
391
|
+
return callback(err, data)
|
|
277
392
|
}
|
|
278
|
-
return callback(err, data)
|
|
393
|
+
if (!data) return callback(err, data)
|
|
394
|
+
restoreUndiciResponseBodyAfterReadForLog(data).then(({ logStr, data: restored }) => {
|
|
395
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
396
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
397
|
+
if (logStr != null) meta.responseBody = logStr
|
|
398
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${restored.statusCode} ${duration}ms`, meta)
|
|
399
|
+
}
|
|
400
|
+
return callback(null, restored)
|
|
401
|
+
}).catch(() => {
|
|
402
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
403
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
|
|
404
|
+
}
|
|
405
|
+
return callback(null, data)
|
|
406
|
+
})
|
|
279
407
|
} : undefined
|
|
280
408
|
const ret = wrappedCb ? origReq.call(this, opts, wrappedCb) : origReq.call(this, opts)
|
|
281
409
|
if (ret && typeof ret.then === 'function' && !wrappedCb) {
|
|
282
410
|
return ret.then(
|
|
283
|
-
(data) => {
|
|
411
|
+
async (data) => {
|
|
284
412
|
const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
|
|
285
|
-
if (HTTP_CLIENT_MODE !== 'off' && !skipLog && data)
|
|
413
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog && data) {
|
|
414
|
+
const { logStr, data: restored } = await restoreUndiciResponseBodyAfterReadForLog(data)
|
|
415
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
416
|
+
if (logStr != null) meta.responseBody = logStr
|
|
417
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${restored.statusCode} ${duration}ms`, meta)
|
|
418
|
+
return restored
|
|
419
|
+
}
|
|
286
420
|
return data
|
|
287
421
|
},
|
|
288
422
|
(err) => {
|
|
@@ -333,9 +467,7 @@ try {
|
|
|
333
467
|
const HTTP_CLIENT_MODE = deps ? deps.HTTP_CLIENT_MODE : 'all'
|
|
334
468
|
d.request = function (opts, callback) {
|
|
335
469
|
const methodStr = String((opts && opts.method) || 'GET').toUpperCase()
|
|
336
|
-
const
|
|
337
|
-
const pathPart = (opts && opts.path) != null ? opts.path : '/'
|
|
338
|
-
const urlStr = origin ? (origin + (pathPart.startsWith('/') ? pathPart : '/' + pathPart)) : 'unknown'
|
|
470
|
+
const urlStr = resolveUrlStrFromDispatcherOpts(opts)
|
|
339
471
|
httpLog('WRAPPER globalThis.dispatcher.request ' + methodStr + ' ' + urlStr.slice(0, 80))
|
|
340
472
|
const _dp = process.env.AZIFY_DEBUG_LOG_PATH
|
|
341
473
|
if (_dp) { try { require('fs').appendFileSync(_dp, new Date().toISOString() + ' [AZIFY] WRAPPER globalDispatcher.request ' + methodStr + ' ' + urlStr.slice(0, 100) + '\n') } catch (_) {} }
|
|
@@ -344,25 +476,48 @@ try {
|
|
|
344
476
|
const traceCtx = ctx || otelCtx
|
|
345
477
|
const rawTraceId = (traceCtx && traceCtx.traceId) || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
346
478
|
const traceId = typeof rawTraceId === 'string' ? (toTraceIdHex ? toTraceIdHex(rawTraceId) : rawTraceId) : rawTraceId
|
|
479
|
+
const requestBodyStr = serializeUndiciRequestBody(opts || {})
|
|
347
480
|
const requestMeta = { traceId, spanId: (traceCtx && traceCtx.spanId) || null, parentSpanId: (traceCtx && traceCtx.parentSpanId) || null, requestId: (traceCtx && traceCtx.requestId) || null, method: methodStr, url: urlStr }
|
|
481
|
+
if (requestBodyStr != null) requestMeta.requestBody = requestBodyStr
|
|
348
482
|
if (markSource) markSource(requestMeta, 'http-client')
|
|
349
483
|
const start = require('perf_hooks').performance.now()
|
|
350
484
|
const skipLog = isLoggerUrl(urlStr)
|
|
351
485
|
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) send('info', `[REQUEST] ${methodStr} ${urlStr}`, requestMeta)
|
|
352
486
|
const wrappedCb = typeof callback === 'function' ? function (err, data) {
|
|
353
487
|
const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
|
|
354
|
-
if (
|
|
355
|
-
if (
|
|
356
|
-
|
|
488
|
+
if (err) {
|
|
489
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
490
|
+
send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
|
|
491
|
+
}
|
|
492
|
+
return callback(err, data)
|
|
357
493
|
}
|
|
358
|
-
return callback(err, data)
|
|
494
|
+
if (!data) return callback(err, data)
|
|
495
|
+
restoreUndiciResponseBodyAfterReadForLog(data).then(({ logStr, data: restored }) => {
|
|
496
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
497
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
498
|
+
if (logStr != null) meta.responseBody = logStr
|
|
499
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${restored.statusCode} ${duration}ms`, meta)
|
|
500
|
+
}
|
|
501
|
+
return callback(null, restored)
|
|
502
|
+
}).catch(() => {
|
|
503
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
|
|
504
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
|
|
505
|
+
}
|
|
506
|
+
return callback(null, data)
|
|
507
|
+
})
|
|
359
508
|
} : undefined
|
|
360
509
|
const ret = wrappedCb ? origReq.call(this, opts, wrappedCb) : origReq.call(this, opts)
|
|
361
510
|
if (ret && typeof ret.then === 'function' && !wrappedCb) {
|
|
362
511
|
return ret.then(
|
|
363
|
-
(data) => {
|
|
512
|
+
async (data) => {
|
|
364
513
|
const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
|
|
365
|
-
if (HTTP_CLIENT_MODE !== 'off' && !skipLog && data)
|
|
514
|
+
if (HTTP_CLIENT_MODE !== 'off' && !skipLog && data) {
|
|
515
|
+
const { logStr, data: restored } = await restoreUndiciResponseBodyAfterReadForLog(data)
|
|
516
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
517
|
+
if (logStr != null) meta.responseBody = logStr
|
|
518
|
+
send('info', `[RESPONSE] ${methodStr} ${urlStr} ${restored.statusCode} ${duration}ms`, meta)
|
|
519
|
+
return restored
|
|
520
|
+
}
|
|
366
521
|
return data
|
|
367
522
|
},
|
|
368
523
|
(err) => {
|
|
@@ -608,14 +763,12 @@ try {
|
|
|
608
763
|
const RequestHandler = mod.exports.RequestHandler
|
|
609
764
|
mod.exports = function wrappedApiRequest(opts, callback) {
|
|
610
765
|
const methodStr = String((opts && opts.method) || 'GET').toUpperCase()
|
|
611
|
-
const
|
|
766
|
+
const urlStr = resolveUrlStrFromDispatcherOpts(opts)
|
|
767
|
+
const urlPart = urlStr !== 'unknown' ? urlStr.slice(0, 80) : ((opts && opts.origin) ? String(opts.origin).slice(0, 80) : (opts && opts.path) || '')
|
|
612
768
|
if (HTTP_DEBUG) try { process.stderr.write('[AZIFY-PATCH] api-request ' + methodStr + ' ' + urlPart + '\n') } catch (_) {}
|
|
613
769
|
if (HTTP_VERBOSE || HTTP_DEBUG) {
|
|
614
770
|
try { process.stderr.write('[AZIFY] HTTP wrapper api-request ' + methodStr + ' ' + urlPart + '\n') } catch (_) {}
|
|
615
771
|
}
|
|
616
|
-
const origin = (opts && opts.origin) || ''
|
|
617
|
-
const pathPart = (opts && opts.path) != null ? opts.path : '/'
|
|
618
|
-
const urlStr = origin ? (origin + (pathPart.startsWith('/') ? pathPart : '/' + pathPart)) : 'unknown'
|
|
619
772
|
httpLog('WRAPPER api-request ' + methodStr + ' ' + urlStr.slice(0, 80))
|
|
620
773
|
const ctx = getRequestContext() || getLastJobContext()
|
|
621
774
|
const otelCtx = getOtelTraceContext()
|
|
@@ -692,6 +845,7 @@ try {
|
|
|
692
845
|
function patchGlobalFetch() {
|
|
693
846
|
try {
|
|
694
847
|
if (typeof globalThis.fetch !== 'function') return
|
|
848
|
+
if (globalThis.fetch.__azifyLoggerFetchPatched) return
|
|
695
849
|
if (globalThis.fetch.__azifyPatched) return
|
|
696
850
|
const deps = loadStoreAndSampling()
|
|
697
851
|
if (!deps || deps.HTTP_CLIENT_MODE === 'off') return
|
package/register.js
CHANGED
|
@@ -10,7 +10,12 @@ try {
|
|
|
10
10
|
}
|
|
11
11
|
const ModuleForRequire = require('module')
|
|
12
12
|
const nodeRequireOriginal = ModuleForRequire.prototype.require
|
|
13
|
-
|
|
13
|
+
let bunyan = null
|
|
14
|
+
try {
|
|
15
|
+
bunyan = require('bunyan')
|
|
16
|
+
} catch (_) {
|
|
17
|
+
/* bunyan é opcional; sem ele o patch Bunyan não aplica, mas HTTP/Pino/axios devem continuar */
|
|
18
|
+
}
|
|
14
19
|
const createBunyanStream = require('./streams/bunyan')
|
|
15
20
|
const { createHttpLoggerTransport } = require('./streams/httpQueue')
|
|
16
21
|
const { getRequestContext } = require('./store')
|
|
@@ -145,12 +150,34 @@ try {
|
|
|
145
150
|
|
|
146
151
|
const debug = process.env.AZIFY_LOGGER_DEBUG === '1'
|
|
147
152
|
const httpVerbose = process.env.AZIFY_LOGGER_HTTP_VERBOSE === '1'
|
|
153
|
+
|
|
154
|
+
function extractUrlFromReqResMessage(msgStr) {
|
|
155
|
+
const s = String(msgStr)
|
|
156
|
+
const m1 = s.match(/\[(?:REQUEST|RESPONSE)\]\s+\S+\s+(https?:\/\/\S+)/i)
|
|
157
|
+
if (m1) return m1[1].replace(/[)\]}>.,;]+$/, '')
|
|
158
|
+
const m2 = s.match(/\[(?:REQUEST|RESPONSE)\]\s+\S+\s+(\S+)/i)
|
|
159
|
+
if (m2 && /^https?:\/\//i.test(m2[1])) return m2[1].replace(/[)\]}>.,;]+$/, '')
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const OPAQUE_NO_URL = 'https://opaque.invalid/azify-logger-no-url'
|
|
164
|
+
|
|
148
165
|
function sendOutboundLog(level, message, meta) {
|
|
149
166
|
try {
|
|
150
167
|
const msgStr = String(message)
|
|
151
168
|
const isReqRes = msgStr.includes('[REQUEST]') || msgStr.includes('[RESPONSE]')
|
|
169
|
+
if (isReqRes && meta && typeof meta === 'object') {
|
|
170
|
+
const u = meta.url
|
|
171
|
+
const badUrl =
|
|
172
|
+
u === 'unknown' ||
|
|
173
|
+
!String(u || '').trim() ||
|
|
174
|
+
String(u).toLowerCase() === 'unknown'
|
|
175
|
+
if (badUrl) {
|
|
176
|
+
const fromMsg = extractUrlFromReqResMessage(msgStr)
|
|
177
|
+
meta = fromMsg ? { ...meta, url: fromMsg } : { ...meta, url: OPAQUE_NO_URL }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
152
180
|
if (isReqRes && meta && meta.url && isLoggerApiCall({ url: meta.url })) return
|
|
153
|
-
if (isReqRes && meta && (meta.url === 'unknown' || !String(meta.url || '').trim() || String(meta.url).toLowerCase() === 'unknown')) return
|
|
154
181
|
const source = meta && meta.__source
|
|
155
182
|
if (debug && source === 'pino-stdout') {
|
|
156
183
|
if (debug) try { process.stderr.write('[azify-logger] SENDOUTBOUND_ENTER level=' + level + ' source=' + source + ' msg=' + msgStr.slice(0, 60) + '\n') } catch (_) {}
|
|
@@ -350,9 +377,16 @@ try {
|
|
|
350
377
|
|
|
351
378
|
try {
|
|
352
379
|
const target = new URL(candidate, normalizedLoggerOrigin)
|
|
353
|
-
|
|
380
|
+
if (loggerEndpoint.hostname.toLowerCase() !== target.hostname.toLowerCase()) {
|
|
381
|
+
return false
|
|
382
|
+
}
|
|
383
|
+
const portLogger = loggerEndpoint.port || (loggerEndpoint.protocol === 'https:' ? '443' : '80')
|
|
384
|
+
const portTarget = target.port || (target.protocol === 'https:' ? '443' : '80')
|
|
385
|
+
if (String(portLogger) !== String(portTarget)) {
|
|
386
|
+
return false
|
|
387
|
+
}
|
|
354
388
|
const targetPath = normalizePath(target.pathname)
|
|
355
|
-
return
|
|
389
|
+
return targetPath === normalizedLoggerPath || targetPath.startsWith(normalizedLoggerPath + '/')
|
|
356
390
|
} catch (_) {
|
|
357
391
|
if (typeof candidate === 'string') {
|
|
358
392
|
const relativePath = normalizePath(candidate)
|
|
@@ -363,22 +397,24 @@ try {
|
|
|
363
397
|
return false
|
|
364
398
|
}
|
|
365
399
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
400
|
+
if (bunyan && typeof bunyan.createLogger === 'function') {
|
|
401
|
+
const originalCreate = bunyan.createLogger
|
|
402
|
+
bunyan.createLogger = function patchedCreateLogger(options) {
|
|
403
|
+
const logger = originalCreate.call(bunyan, options)
|
|
404
|
+
try {
|
|
405
|
+
const level = process.env.AZIFY_LOG_LEVEL || (options && options.level) || 'info'
|
|
406
|
+
const loggerUrl = process.env.AZIFY_LOGGER_URL
|
|
407
|
+
const serviceName = process.env.APP_NAME || (options && options.name)
|
|
408
|
+
const environment = process.env.NODE_ENV
|
|
409
|
+
|
|
410
|
+
logger.addStream({
|
|
411
|
+
level,
|
|
412
|
+
type: 'raw',
|
|
413
|
+
stream: createBunyanStream({ loggerUrl, serviceName, environment })
|
|
414
|
+
})
|
|
415
|
+
} catch (_) {}
|
|
416
|
+
return logger
|
|
417
|
+
}
|
|
382
418
|
}
|
|
383
419
|
|
|
384
420
|
const getOtelTraceContext = () => {
|
|
@@ -409,16 +445,24 @@ try {
|
|
|
409
445
|
const { randomBytes } = require('crypto')
|
|
410
446
|
const { performance } = require('perf_hooks')
|
|
411
447
|
const { getRequestContext, getLastJobContext, toTraceIdHex } = require('./store')
|
|
448
|
+
const {
|
|
449
|
+
resolveUndiciRequestOpts,
|
|
450
|
+
serializeUndiciRequestBody,
|
|
451
|
+
restoreUndiciResponseBodyAfterReadForLog
|
|
452
|
+
} = require('./utils/undiciLogBodies')
|
|
412
453
|
const origRequest = exports.request
|
|
413
454
|
exports.request = function(url, options, callback) {
|
|
414
455
|
const ctx = getRequestContext() || getLastJobContext()
|
|
415
456
|
const otelCtx = getOtelTraceContext()
|
|
416
457
|
const traceCtx = ctx || otelCtx
|
|
417
|
-
const
|
|
458
|
+
const reqOpts = resolveUndiciRequestOpts(url, options)
|
|
459
|
+
const method = (reqOpts.method || options?.method || 'GET').toUpperCase()
|
|
418
460
|
const urlString = typeof url === 'string' ? url : (url && url.toString && url.toString()) || 'unknown'
|
|
419
461
|
const rawTraceId = traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
420
462
|
const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
|
|
463
|
+
const requestBodyStr = serializeUndiciRequestBody(reqOpts)
|
|
421
464
|
const requestMeta = { traceId, spanId: traceCtx?.spanId || null, parentSpanId: traceCtx?.parentSpanId || null, requestId: traceCtx?.requestId || null, method, url: urlString }
|
|
465
|
+
if (requestBodyStr != null) requestMeta.requestBody = requestBodyStr
|
|
422
466
|
markSource(requestMeta, 'http-client')
|
|
423
467
|
const startTime = performance.now()
|
|
424
468
|
if (HTTP_CLIENT_MODE !== 'off') {
|
|
@@ -426,20 +470,39 @@ try {
|
|
|
426
470
|
}
|
|
427
471
|
const wrappedCb = callback ? function(err, data) {
|
|
428
472
|
const duration = Number((performance.now() - startTime).toFixed(2))
|
|
429
|
-
if (
|
|
430
|
-
if (
|
|
431
|
-
|
|
473
|
+
if (err) {
|
|
474
|
+
if (HTTP_CLIENT_MODE !== 'off') {
|
|
475
|
+
sendOutboundLog('error', `[RESPONSE] ${method} ${urlString} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
|
|
476
|
+
}
|
|
477
|
+
return callback(err, data)
|
|
432
478
|
}
|
|
433
|
-
return callback(err, data)
|
|
479
|
+
if (!data) return callback(err, data)
|
|
480
|
+
restoreUndiciResponseBodyAfterReadForLog(data).then(({ logStr, data: restored }) => {
|
|
481
|
+
if (HTTP_CLIENT_MODE !== 'off') {
|
|
482
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
483
|
+
if (logStr != null) meta.responseBody = logStr
|
|
484
|
+
sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${restored.statusCode} ${duration}ms`, meta)
|
|
485
|
+
}
|
|
486
|
+
return callback(null, restored)
|
|
487
|
+
}).catch(() => {
|
|
488
|
+
if (HTTP_CLIENT_MODE !== 'off') {
|
|
489
|
+
sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
|
|
490
|
+
}
|
|
491
|
+
return callback(null, data)
|
|
492
|
+
})
|
|
434
493
|
} : undefined
|
|
435
494
|
if (wrappedCb) return origRequest.call(this, url, options, wrappedCb)
|
|
436
495
|
const promise = origRequest.call(this, url, options)
|
|
437
496
|
if (promise && typeof promise.then === 'function') {
|
|
438
497
|
return promise.then(
|
|
439
|
-
(data) => {
|
|
498
|
+
async (data) => {
|
|
440
499
|
const duration = Number((performance.now() - startTime).toFixed(2))
|
|
441
500
|
if (HTTP_CLIENT_MODE !== 'off' && data) {
|
|
442
|
-
|
|
501
|
+
const { logStr, data: restored } = await restoreUndiciResponseBodyAfterReadForLog(data)
|
|
502
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
503
|
+
if (logStr != null) meta.responseBody = logStr
|
|
504
|
+
sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${restored.statusCode} ${duration}ms`, meta)
|
|
505
|
+
return restored
|
|
443
506
|
}
|
|
444
507
|
return data
|
|
445
508
|
},
|
|
@@ -468,7 +531,9 @@ try {
|
|
|
468
531
|
const urlString = origin ? (origin + (path.startsWith('/') ? path : '/' + path)) : 'unknown'
|
|
469
532
|
const rawTraceId = traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
470
533
|
const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
|
|
534
|
+
const requestBodyStr = serializeUndiciRequestBody(opts || {})
|
|
471
535
|
const requestMeta = { traceId, spanId: traceCtx?.spanId || null, parentSpanId: traceCtx?.parentSpanId || null, requestId: traceCtx?.requestId || null, method: methodStr, url: urlString }
|
|
536
|
+
if (requestBodyStr != null) requestMeta.requestBody = requestBodyStr
|
|
472
537
|
markSource(requestMeta, 'http-client')
|
|
473
538
|
const startTime = performance.now()
|
|
474
539
|
if (HTTP_CLIENT_MODE !== 'off') {
|
|
@@ -476,19 +541,38 @@ try {
|
|
|
476
541
|
}
|
|
477
542
|
const wrappedCb = typeof callback === 'function' ? function(err, data) {
|
|
478
543
|
const duration = Number((performance.now() - startTime).toFixed(2))
|
|
479
|
-
if (
|
|
480
|
-
if (
|
|
481
|
-
|
|
544
|
+
if (err) {
|
|
545
|
+
if (HTTP_CLIENT_MODE !== 'off') {
|
|
546
|
+
sendOutboundLog('error', `[RESPONSE] ${methodStr} ${urlString} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
|
|
547
|
+
}
|
|
548
|
+
return callback(err, data)
|
|
482
549
|
}
|
|
483
|
-
return callback(err, data)
|
|
550
|
+
if (!data) return callback(err, data)
|
|
551
|
+
restoreUndiciResponseBodyAfterReadForLog(data).then(({ logStr, data: restored }) => {
|
|
552
|
+
if (HTTP_CLIENT_MODE !== 'off') {
|
|
553
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
554
|
+
if (logStr != null) meta.responseBody = logStr
|
|
555
|
+
sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${restored.statusCode} ${duration}ms`, meta)
|
|
556
|
+
}
|
|
557
|
+
return callback(null, restored)
|
|
558
|
+
}).catch(() => {
|
|
559
|
+
if (HTTP_CLIENT_MODE !== 'off') {
|
|
560
|
+
sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
|
|
561
|
+
}
|
|
562
|
+
return callback(null, data)
|
|
563
|
+
})
|
|
484
564
|
} : undefined
|
|
485
565
|
const ret = wrappedCb ? origDispRequest.call(this, opts, wrappedCb) : origDispRequest.call(this, opts)
|
|
486
566
|
if (ret && typeof ret.then === 'function' && !wrappedCb) {
|
|
487
567
|
return ret.then(
|
|
488
|
-
(data) => {
|
|
568
|
+
async (data) => {
|
|
489
569
|
const duration = Number((performance.now() - startTime).toFixed(2))
|
|
490
570
|
if (HTTP_CLIENT_MODE !== 'off' && data) {
|
|
491
|
-
|
|
571
|
+
const { logStr, data: restored } = await restoreUndiciResponseBodyAfterReadForLog(data)
|
|
572
|
+
const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
|
|
573
|
+
if (logStr != null) meta.responseBody = logStr
|
|
574
|
+
sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${restored.statusCode} ${duration}ms`, meta)
|
|
575
|
+
return restored
|
|
492
576
|
}
|
|
493
577
|
return data
|
|
494
578
|
},
|
|
@@ -1552,6 +1636,35 @@ try {
|
|
|
1552
1636
|
return url
|
|
1553
1637
|
}
|
|
1554
1638
|
|
|
1639
|
+
const MAX_HTTP_BODY_CHARS = Math.min(
|
|
1640
|
+
Math.max(parseInt(String(process.env.AZIFY_LOGGER_HTTP_BODY_MAX_CHARS || '5000'), 10) || 5000, 100),
|
|
1641
|
+
100000
|
|
1642
|
+
)
|
|
1643
|
+
function clipHttpLogString(s) {
|
|
1644
|
+
if (typeof s !== 'string') return s
|
|
1645
|
+
return s.length > MAX_HTTP_BODY_CHARS ? s.slice(0, MAX_HTTP_BODY_CHARS) : s
|
|
1646
|
+
}
|
|
1647
|
+
function stringifyHttpBodyForLog(value) {
|
|
1648
|
+
if (value == null) return null
|
|
1649
|
+
try {
|
|
1650
|
+
if (typeof value === 'string') {
|
|
1651
|
+
return clipHttpLogString(value)
|
|
1652
|
+
}
|
|
1653
|
+
if (Buffer.isBuffer(value)) {
|
|
1654
|
+
return clipHttpLogString(value.toString('utf8'))
|
|
1655
|
+
}
|
|
1656
|
+
if (value && typeof value.pipe === 'function') {
|
|
1657
|
+
return '[Stream]'
|
|
1658
|
+
}
|
|
1659
|
+
if (typeof value === 'object') {
|
|
1660
|
+
return clipHttpLogString(JSON.stringify(value))
|
|
1661
|
+
}
|
|
1662
|
+
return clipHttpLogString(String(value))
|
|
1663
|
+
} catch (_) {
|
|
1664
|
+
return '[unserializable]'
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1555
1668
|
const patchInstance = (instance) => {
|
|
1556
1669
|
if (instance.__azifyLoggerPatched) {
|
|
1557
1670
|
return instance
|
|
@@ -1595,6 +1708,11 @@ try {
|
|
|
1595
1708
|
headers: config.headers
|
|
1596
1709
|
}
|
|
1597
1710
|
|
|
1711
|
+
const requestBodyString = stringifyHttpBodyForLog(config.data)
|
|
1712
|
+
if (requestBodyString != null) {
|
|
1713
|
+
requestMeta.requestBody = requestBodyString
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1598
1716
|
markSource(requestMeta, 'http-client')
|
|
1599
1717
|
config.__azifyLogger = {
|
|
1600
1718
|
meta: requestMeta,
|
|
@@ -1648,12 +1766,15 @@ try {
|
|
|
1648
1766
|
|
|
1649
1767
|
const stringifyBody = (value) => {
|
|
1650
1768
|
if (value == null) return null
|
|
1651
|
-
if (typeof value === 'string') return value
|
|
1769
|
+
if (typeof value === 'string') return clipHttpLogString(value)
|
|
1770
|
+
if (Buffer.isBuffer(value)) {
|
|
1771
|
+
return clipHttpLogString(value.toString('utf8'))
|
|
1772
|
+
}
|
|
1652
1773
|
try {
|
|
1653
|
-
return JSON.stringify(value)
|
|
1774
|
+
return clipHttpLogString(JSON.stringify(value))
|
|
1654
1775
|
} catch (_) {
|
|
1655
1776
|
try {
|
|
1656
|
-
return String(value)
|
|
1777
|
+
return clipHttpLogString(String(value))
|
|
1657
1778
|
} catch (_) {
|
|
1658
1779
|
return null
|
|
1659
1780
|
}
|
|
@@ -1662,17 +1783,16 @@ try {
|
|
|
1662
1783
|
|
|
1663
1784
|
if (marker && marker.meta) {
|
|
1664
1785
|
duration = Number((performance.now() - marker.start).toFixed(2))
|
|
1665
|
-
|
|
1666
|
-
if (typeof responseBodyString === 'string' && responseBodyString.length > 5000) {
|
|
1667
|
-
responseBodyString = responseBodyString.slice(0, 5000)
|
|
1668
|
-
}
|
|
1786
|
+
const responseBodyString = stringifyBody(response.data)
|
|
1669
1787
|
meta = {
|
|
1670
1788
|
...marker.meta,
|
|
1671
1789
|
url: finalUrl,
|
|
1672
1790
|
statusCode: response.status,
|
|
1673
1791
|
responseTimeMs: duration,
|
|
1674
|
-
responseHeaders: response.headers
|
|
1675
|
-
|
|
1792
|
+
responseHeaders: response.headers
|
|
1793
|
+
}
|
|
1794
|
+
if (responseBodyString != null) {
|
|
1795
|
+
meta.responseBody = responseBodyString
|
|
1676
1796
|
}
|
|
1677
1797
|
} else {
|
|
1678
1798
|
const requestHeaders = response.config?.headers || {}
|
|
@@ -1681,10 +1801,7 @@ try {
|
|
|
1681
1801
|
const spanId = requestHeaders['x-span-id'] || requestHeaders['X-Span-ID'] || randomBytes(8).toString('hex')
|
|
1682
1802
|
const parentSpanId = requestHeaders['x-parent-span-id'] || requestHeaders['X-Parent-Span-ID'] || ctx?.spanId || null
|
|
1683
1803
|
const requestId = requestHeaders['x-request-id'] || requestHeaders['X-Request-ID'] || ctx?.requestId || randomUUID()
|
|
1684
|
-
|
|
1685
|
-
if (typeof responseBodyString === 'string' && responseBodyString.length > 5000) {
|
|
1686
|
-
responseBodyString = responseBodyString.slice(0, 5000)
|
|
1687
|
-
}
|
|
1804
|
+
const responseBodyString = stringifyBody(response.data)
|
|
1688
1805
|
|
|
1689
1806
|
meta = {
|
|
1690
1807
|
traceId,
|
|
@@ -1695,8 +1812,10 @@ try {
|
|
|
1695
1812
|
url: finalUrl,
|
|
1696
1813
|
statusCode: response.status,
|
|
1697
1814
|
responseTimeMs: duration,
|
|
1698
|
-
responseHeaders: response.headers
|
|
1699
|
-
|
|
1815
|
+
responseHeaders: response.headers
|
|
1816
|
+
}
|
|
1817
|
+
if (responseBodyString != null) {
|
|
1818
|
+
meta.responseBody = responseBodyString
|
|
1700
1819
|
}
|
|
1701
1820
|
}
|
|
1702
1821
|
|
|
@@ -1895,19 +2014,35 @@ try {
|
|
|
1895
2014
|
} catch (_) {
|
|
1896
2015
|
}
|
|
1897
2016
|
|
|
2017
|
+
const maxFetchBodyChars = Math.min(
|
|
2018
|
+
Math.max(parseInt(String(process.env.AZIFY_LOGGER_HTTP_BODY_MAX_CHARS || '5000'), 10) || 5000, 100),
|
|
2019
|
+
100000
|
|
2020
|
+
)
|
|
2021
|
+
const clipFetchBody = (s) => {
|
|
2022
|
+
if (typeof s !== 'string' || !s.length) return null
|
|
2023
|
+
return s.length > maxFetchBodyChars ? s.slice(0, maxFetchBodyChars) : s
|
|
2024
|
+
}
|
|
2025
|
+
|
|
1898
2026
|
let requestBodyString = null
|
|
1899
|
-
|
|
1900
|
-
|
|
2027
|
+
try {
|
|
2028
|
+
if (init && init.body !== null && init.body !== undefined) {
|
|
1901
2029
|
if (typeof init.body === 'string') {
|
|
1902
|
-
requestBodyString = init.body
|
|
2030
|
+
requestBodyString = clipFetchBody(init.body)
|
|
1903
2031
|
} else if (init.body instanceof FormData || init.body instanceof URLSearchParams) {
|
|
1904
2032
|
requestBodyString = '[FormData/URLSearchParams]'
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
} catch (_) {
|
|
2036
|
+
}
|
|
2037
|
+
if (requestBodyString == null && request.body != null && method !== 'GET' && method !== 'HEAD') {
|
|
2038
|
+
try {
|
|
2039
|
+
const clone = request.clone()
|
|
2040
|
+
const text = await Promise.race([
|
|
2041
|
+
clone.text(),
|
|
2042
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 4000))
|
|
2043
|
+
])
|
|
2044
|
+
if (typeof text === 'string' && text.length) {
|
|
2045
|
+
requestBodyString = clipFetchBody(text)
|
|
1911
2046
|
}
|
|
1912
2047
|
} catch (_) {
|
|
1913
2048
|
}
|
|
@@ -2014,10 +2149,15 @@ try {
|
|
|
2014
2149
|
}
|
|
2015
2150
|
}
|
|
2016
2151
|
|
|
2152
|
+
const responseReadMs = Math.min(
|
|
2153
|
+
Math.max(parseInt(String(process.env.AZIFY_LOGGER_HTTP_RESPONSE_READ_MS || '4000'), 10) || 4000, 200),
|
|
2154
|
+
30000
|
|
2155
|
+
)
|
|
2156
|
+
|
|
2017
2157
|
if (contentType.includes('application/json') || contentType.includes('text/')) {
|
|
2018
2158
|
try {
|
|
2019
2159
|
const clonedResponse = response.clone()
|
|
2020
|
-
const bodyText = await readBodyWithTimeout(clonedResponse,
|
|
2160
|
+
const bodyText = await readBodyWithTimeout(clonedResponse, responseReadMs)
|
|
2021
2161
|
if (bodyText) {
|
|
2022
2162
|
if (contentType.includes('application/json')) {
|
|
2023
2163
|
try {
|
|
@@ -2028,13 +2168,13 @@ try {
|
|
|
2028
2168
|
} else {
|
|
2029
2169
|
responseBodyString = bodyText
|
|
2030
2170
|
}
|
|
2031
|
-
if (typeof responseBodyString === 'string' && responseBodyString.length >
|
|
2032
|
-
responseBodyString = responseBodyString.slice(0,
|
|
2171
|
+
if (typeof responseBodyString === 'string' && responseBodyString.length > maxFetchBodyChars) {
|
|
2172
|
+
responseBodyString = responseBodyString.slice(0, maxFetchBodyChars)
|
|
2033
2173
|
}
|
|
2034
2174
|
}
|
|
2035
2175
|
} catch (_) {
|
|
2036
2176
|
try {
|
|
2037
|
-
const bodyText = await readBodyWithTimeout(response,
|
|
2177
|
+
const bodyText = await readBodyWithTimeout(response, responseReadMs)
|
|
2038
2178
|
if (bodyText) {
|
|
2039
2179
|
if (contentType.includes('application/json')) {
|
|
2040
2180
|
try {
|
|
@@ -2045,13 +2185,23 @@ try {
|
|
|
2045
2185
|
} else {
|
|
2046
2186
|
responseBodyString = bodyText
|
|
2047
2187
|
}
|
|
2048
|
-
if (typeof responseBodyString === 'string' && responseBodyString.length >
|
|
2049
|
-
responseBodyString = responseBodyString.slice(0,
|
|
2188
|
+
if (typeof responseBodyString === 'string' && responseBodyString.length > maxFetchBodyChars) {
|
|
2189
|
+
responseBodyString = responseBodyString.slice(0, maxFetchBodyChars)
|
|
2050
2190
|
}
|
|
2051
2191
|
}
|
|
2052
2192
|
} catch (_) {
|
|
2053
2193
|
}
|
|
2054
2194
|
}
|
|
2195
|
+
} else if (response.status !== 204 && response.status !== 205 && response.status !== 304) {
|
|
2196
|
+
try {
|
|
2197
|
+
const clonedResponse = response.clone()
|
|
2198
|
+
const bodyText = await readBodyWithTimeout(clonedResponse, responseReadMs)
|
|
2199
|
+
if (bodyText && bodyText.length) {
|
|
2200
|
+
responseBodyString =
|
|
2201
|
+
bodyText.length > maxFetchBodyChars ? bodyText.slice(0, maxFetchBodyChars) : bodyText
|
|
2202
|
+
}
|
|
2203
|
+
} catch (_) {
|
|
2204
|
+
}
|
|
2055
2205
|
}
|
|
2056
2206
|
} catch (_) {
|
|
2057
2207
|
}
|
package/scripts/redis-worker.js
CHANGED
|
@@ -254,6 +254,9 @@ function sanitizePayload(payload) {
|
|
|
254
254
|
if (sanitized.meta.responseBody) {
|
|
255
255
|
sanitized.meta.responseBody = sanitizeBody(sanitized.meta.responseBody)
|
|
256
256
|
}
|
|
257
|
+
if (sanitized.meta.requestBody) {
|
|
258
|
+
sanitized.meta.requestBody = sanitizeBody(sanitized.meta.requestBody)
|
|
259
|
+
}
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
return sanitized
|
package/server.js
CHANGED
|
@@ -186,7 +186,11 @@ async function ensureIndexTemplate() {
|
|
|
186
186
|
settings: {
|
|
187
187
|
number_of_shards: 1,
|
|
188
188
|
number_of_replicas: 0,
|
|
189
|
-
'index.refresh_interval': '5s'
|
|
189
|
+
'index.refresh_interval': '5s',
|
|
190
|
+
'index.mapping.total_fields.limit': Math.min(
|
|
191
|
+
Math.max(Number(process.env.AZIFY_LOGGER_OPENSEARCH_TOTAL_FIELDS_LIMIT) || 5000, 1000),
|
|
192
|
+
20000
|
|
193
|
+
)
|
|
190
194
|
},
|
|
191
195
|
mappings: {
|
|
192
196
|
properties: {
|
|
@@ -2327,14 +2331,22 @@ function _drainOpenSearchQueue() {
|
|
|
2327
2331
|
if (_openSearchCircuitRetryTimer.unref) _openSearchCircuitRetryTimer.unref()
|
|
2328
2332
|
}
|
|
2329
2333
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2334
|
+
const status = error?.response?.status
|
|
2335
|
+
const rawMsg = error?.response?.data?.error?.reason ||
|
|
2336
|
+
error?.response?.data?.error?.type ||
|
|
2337
|
+
error?.response?.data?.message ||
|
|
2338
|
+
error?.message ||
|
|
2339
|
+
'Erro desconhecido'
|
|
2340
|
+
const errorMsg = typeof rawMsg === 'string' ? rawMsg.slice(0, 800) : String(rawMsg).slice(0, 800)
|
|
2341
|
+
const logPayload = {
|
|
2342
|
+
index: job.indexName,
|
|
2343
|
+
serviceName: job.serviceName,
|
|
2344
|
+
status,
|
|
2345
|
+
message: errorMsg
|
|
2346
|
+
}
|
|
2347
|
+
console.error('❌ Falha ao enviar log para OpenSearch', logPayload)
|
|
2348
|
+
if (error?.code && _openSearchFailuresInRow <= 10) {
|
|
2349
|
+
console.error('⚙️ Código de erro:', error.code)
|
|
2338
2350
|
}
|
|
2339
2351
|
})
|
|
2340
2352
|
.finally(() => {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function maxBodyChars() {
|
|
4
|
+
return Math.min(
|
|
5
|
+
Math.max(parseInt(String(process.env.AZIFY_LOGGER_HTTP_BODY_MAX_CHARS || '5000'), 10) || 5000, 100),
|
|
6
|
+
100000
|
|
7
|
+
)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function clip(s) {
|
|
11
|
+
const m = maxBodyChars()
|
|
12
|
+
if (typeof s !== 'string' || !s.length) return null
|
|
13
|
+
return s.length > m ? s.slice(0, m) : s
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** undici: request(url, opts) ou request(opts) — retorna o objeto de opções com body/method. */
|
|
17
|
+
function resolveUndiciRequestOpts(url, options) {
|
|
18
|
+
if (typeof url === 'object' && url !== null && !(url instanceof URL)) {
|
|
19
|
+
return url
|
|
20
|
+
}
|
|
21
|
+
return options && typeof options === 'object' ? options : {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function serializeUndiciRequestBody(opts) {
|
|
25
|
+
if (!opts || opts.body == null || opts.body === undefined) return null
|
|
26
|
+
try {
|
|
27
|
+
if (typeof opts.body === 'string') return clip(opts.body)
|
|
28
|
+
if (Buffer.isBuffer(opts.body)) return clip(opts.body.toString('utf8'))
|
|
29
|
+
if (opts.body && typeof opts.body.pipe === 'function') return '[Stream]'
|
|
30
|
+
return clip(String(opts.body))
|
|
31
|
+
} catch (_) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function responseReadTimeoutMs() {
|
|
37
|
+
return Math.min(
|
|
38
|
+
Math.max(parseInt(String(process.env.AZIFY_LOGGER_HTTP_RESPONSE_READ_MS || '4000'), 10) || 4000, 200),
|
|
39
|
+
30000
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lê o body da resposta undici para log e substitui `data.body` por um objeto com .text()/.json()
|
|
45
|
+
* compatível com o que o undici expõe, para o chamador ainda conseguir fazer parse.
|
|
46
|
+
*/
|
|
47
|
+
async function restoreUndiciResponseBodyAfterReadForLog(data) {
|
|
48
|
+
if (!data || !data.body || typeof data.body.text !== 'function') {
|
|
49
|
+
return { logStr: null, data }
|
|
50
|
+
}
|
|
51
|
+
const timeoutMs = responseReadTimeoutMs()
|
|
52
|
+
try {
|
|
53
|
+
const text = await Promise.race([
|
|
54
|
+
data.body.text(),
|
|
55
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeoutMs))
|
|
56
|
+
])
|
|
57
|
+
if (text == null || typeof text !== 'string') {
|
|
58
|
+
return { logStr: null, data }
|
|
59
|
+
}
|
|
60
|
+
const logStr = clip(text)
|
|
61
|
+
data.body = {
|
|
62
|
+
async text() {
|
|
63
|
+
return text
|
|
64
|
+
},
|
|
65
|
+
async json() {
|
|
66
|
+
return JSON.parse(text)
|
|
67
|
+
},
|
|
68
|
+
async arrayBuffer() {
|
|
69
|
+
return Buffer.from(text, 'utf8').buffer
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { logStr, data }
|
|
73
|
+
} catch (_) {
|
|
74
|
+
return { logStr: null, data }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
resolveUndiciRequestOpts,
|
|
80
|
+
serializeUndiciRequestBody,
|
|
81
|
+
restoreUndiciResponseBodyAfterReadForLog,
|
|
82
|
+
maxBodyChars
|
|
83
|
+
}
|