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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azify-logger",
3
- "version": "1.0.54",
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 loggerUrl = process.env.AZIFY_LOGGER_URL
33
- if (!loggerUrl || typeof urlStr !== 'string' || !urlStr.trim()) return false
34
- const a = String(urlStr).trim().toLowerCase().replace(/\/+$/, '')
35
- const b = String(loggerUrl).trim().toLowerCase().replace(/\/+$/, '')
36
- if (!b) return false
37
- return a === b || a.startsWith(b + '/')
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 = exports && typeof exports.fetch === 'function' && !exports.fetch.__azifyPatched
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
- let urlStr = 'unknown'
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 (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
147
- if (err) send('error', `[RESPONSE] ${method} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
148
- else if (data) send('info', `[RESPONSE] ${method} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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) send('info', `[RESPONSE] ${method} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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 origin = (opts && opts.origin) || ''
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 (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
194
- if (err) send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
195
- else if (data) send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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) send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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 origin = (opts && opts.origin) || ''
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 (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
275
- if (err) send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
276
- else if (data) send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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) send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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 origin = (opts && opts.origin) || ''
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 (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
355
- if (err) send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
356
- else if (data) send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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) send('info', `[RESPONSE] ${methodStr} ${urlStr} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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 urlPart = (opts && opts.origin) ? String(opts.origin).slice(0, 80) : (opts && opts.path) || ''
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
- const bunyan = require('bunyan')
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
- const targetOrigin = `${target.protocol}//${target.host}`
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 targetOrigin === normalizedLoggerOrigin && targetPath === normalizedLoggerPath
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
- const originalCreate = bunyan.createLogger
367
- bunyan.createLogger = function patchedCreateLogger(options) {
368
- const logger = originalCreate.call(bunyan, options)
369
- try {
370
- const level = process.env.AZIFY_LOG_LEVEL || (options && options.level) || 'info'
371
- const loggerUrl = process.env.AZIFY_LOGGER_URL
372
- const serviceName = process.env.APP_NAME || (options && options.name)
373
- const environment = process.env.NODE_ENV
374
-
375
- logger.addStream({
376
- level,
377
- type: 'raw',
378
- stream: createBunyanStream({ loggerUrl, serviceName, environment })
379
- })
380
- } catch (_) {}
381
- return logger
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 method = (options?.method || 'GET').toUpperCase()
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 (HTTP_CLIENT_MODE !== 'off') {
430
- if (err) sendOutboundLog('error', `[RESPONSE] ${method} ${urlString} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
431
- else if (data) sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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
- sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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 (HTTP_CLIENT_MODE !== 'off') {
480
- if (err) sendOutboundLog('error', `[RESPONSE] ${methodStr} ${urlString} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
481
- else if (data) sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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
- sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
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
- let responseBodyString = stringifyBody(response.data)
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
- responseBody: responseBodyString
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
- let responseBodyString = stringifyBody(response.data)
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
- responseBody: responseBodyString
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
- if (init && init.body !== null && init.body !== undefined) {
1900
- try {
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
- } else if (typeof init.body === 'object' && !(init.body instanceof Blob) && !(init.body instanceof ArrayBuffer)) {
1906
- try {
1907
- requestBodyString = JSON.stringify(init.body)
1908
- } catch (_) {
1909
- requestBodyString = String(init.body)
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, 200)
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 > 5000) {
2032
- responseBodyString = responseBodyString.slice(0, 5000)
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, 200)
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 > 5000) {
2049
- responseBodyString = responseBodyString.slice(0, 5000)
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
  }
@@ -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
- if (_openSearchFailuresInRow <= 2) {
2331
- const status = error?.response?.status
2332
- const errorMsg = error?.response?.data?.error?.reason ||
2333
- error?.response?.data?.message ||
2334
- error?.message ||
2335
- 'Erro desconhecido'
2336
- console.error('❌ Falha ao enviar log para OpenSearch', { status, message: errorMsg })
2337
- if (error?.code) console.error('⚙️ Código de erro:', error.code)
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
+ }