azify-logger 1.0.55 → 1.0.56

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.55",
3
+ "version": "1.0.56",
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 {
@@ -155,8 +160,19 @@ try {
155
160
  }
156
161
 
157
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
+ }
158
170
  const hasRequest = exports && typeof exports.request === 'function' && !exports.request.__azifyPatched
159
- 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
160
176
  if (!exports || (!hasRequest && !hasFetch)) return
161
177
  const deps = loadStoreAndSampling()
162
178
  const noDeps = !deps
@@ -188,26 +204,50 @@ try {
188
204
  const traceCtx = ctx || otelCtx
189
205
  const rawTraceId = traceCtx && traceCtx.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
190
206
  const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
207
+ const reqOpts = resolveUndiciRequestOpts(url, options)
208
+ const requestBodyStr = serializeUndiciRequestBody(reqOpts)
191
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
192
211
  markSource(requestMeta, 'http-client')
193
212
  const start = require('perf_hooks').performance.now()
194
213
  const skipLog = isLoggerUrl(urlStr)
195
214
  if (HTTP_CLIENT_MODE !== 'off' && !skipLog) send('info', `[REQUEST] ${method} ${urlStr}`, requestMeta)
196
215
  const wrappedCb = callback ? function (err, data) {
197
216
  const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
198
- if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
199
- if (err) send('error', `[RESPONSE] ${method} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
200
- 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)
201
222
  }
202
- 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
+ })
203
237
  } : undefined
204
238
  if (wrappedCb) return origRequest.call(this, url, options, wrappedCb)
205
239
  const p = origRequest.call(this, url, options)
206
240
  if (p && typeof p.then === 'function') {
207
241
  return p.then(
208
- (data) => {
242
+ async (data) => {
209
243
  const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
210
- 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
+ }
211
251
  return data
212
252
  },
213
253
  (err) => {
@@ -233,25 +273,48 @@ try {
233
273
  const traceCtx = ctx || otelCtx
234
274
  const rawTraceId = (traceCtx && traceCtx.traceId) || (require('crypto').randomUUID && require('crypto').randomUUID())
235
275
  const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
276
+ const requestBodyStr = serializeUndiciRequestBody(opts || {})
236
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
237
279
  markSource(requestMeta, 'http-client')
238
280
  const start = require('perf_hooks').performance.now()
239
281
  const skipLog = isLoggerUrl(urlStr)
240
282
  if (HTTP_CLIENT_MODE !== 'off' && !skipLog) send('info', `[REQUEST] ${methodStr} ${urlStr}`, requestMeta)
241
283
  const wrappedCb = typeof callback === 'function' ? function (err, data) {
242
284
  const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
243
- if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
244
- if (err) send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
245
- 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)
246
290
  }
247
- 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
+ })
248
305
  } : undefined
249
306
  const ret = wrappedCb ? origDisp.call(this, opts, wrappedCb) : origDisp.call(this, opts)
250
307
  if (ret && typeof ret.then === 'function' && !wrappedCb) {
251
308
  return ret.then(
252
- (data) => {
309
+ async (data) => {
253
310
  const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
254
- 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
+ }
255
318
  return data
256
319
  },
257
320
  (err) => {
@@ -312,25 +375,48 @@ try {
312
375
  const traceCtx = ctx || otelCtx
313
376
  const rawTraceId = (traceCtx && traceCtx.traceId) || (require('crypto').randomUUID && require('crypto').randomUUID())
314
377
  const traceId = typeof rawTraceId === 'string' ? (toTraceIdHex ? toTraceIdHex(rawTraceId) : rawTraceId) : rawTraceId
378
+ const requestBodyStr = serializeUndiciRequestBody(opts || {})
315
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
316
381
  if (markSource) markSource(requestMeta, 'http-client')
317
382
  const start = require('perf_hooks').performance.now()
318
383
  const skipLog = isLoggerUrl(urlStr)
319
384
  if (HTTP_CLIENT_MODE !== 'off' && !skipLog) send('info', `[REQUEST] ${methodStr} ${urlStr}`, requestMeta)
320
385
  const wrappedCb = typeof callback === 'function' ? function (err, data) {
321
386
  const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
322
- if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
323
- if (err) send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
324
- 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)
325
392
  }
326
- 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
+ })
327
407
  } : undefined
328
408
  const ret = wrappedCb ? origReq.call(this, opts, wrappedCb) : origReq.call(this, opts)
329
409
  if (ret && typeof ret.then === 'function' && !wrappedCb) {
330
410
  return ret.then(
331
- (data) => {
411
+ async (data) => {
332
412
  const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
333
- 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
+ }
334
420
  return data
335
421
  },
336
422
  (err) => {
@@ -390,25 +476,48 @@ try {
390
476
  const traceCtx = ctx || otelCtx
391
477
  const rawTraceId = (traceCtx && traceCtx.traceId) || (require('crypto').randomUUID && require('crypto').randomUUID())
392
478
  const traceId = typeof rawTraceId === 'string' ? (toTraceIdHex ? toTraceIdHex(rawTraceId) : rawTraceId) : rawTraceId
479
+ const requestBodyStr = serializeUndiciRequestBody(opts || {})
393
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
394
482
  if (markSource) markSource(requestMeta, 'http-client')
395
483
  const start = require('perf_hooks').performance.now()
396
484
  const skipLog = isLoggerUrl(urlStr)
397
485
  if (HTTP_CLIENT_MODE !== 'off' && !skipLog) send('info', `[REQUEST] ${methodStr} ${urlStr}`, requestMeta)
398
486
  const wrappedCb = typeof callback === 'function' ? function (err, data) {
399
487
  const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
400
- if (HTTP_CLIENT_MODE !== 'off' && !skipLog) {
401
- if (err) send('error', `[RESPONSE] ${methodStr} ${urlStr} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
402
- 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)
403
493
  }
404
- 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
+ })
405
508
  } : undefined
406
509
  const ret = wrappedCb ? origReq.call(this, opts, wrappedCb) : origReq.call(this, opts)
407
510
  if (ret && typeof ret.then === 'function' && !wrappedCb) {
408
511
  return ret.then(
409
- (data) => {
512
+ async (data) => {
410
513
  const duration = Number((require('perf_hooks').performance.now() - start).toFixed(2))
411
- 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
+ }
412
521
  return data
413
522
  },
414
523
  (err) => {
@@ -736,6 +845,7 @@ try {
736
845
  function patchGlobalFetch() {
737
846
  try {
738
847
  if (typeof globalThis.fetch !== 'function') return
848
+ if (globalThis.fetch.__azifyLoggerFetchPatched) return
739
849
  if (globalThis.fetch.__azifyPatched) return
740
850
  const deps = loadStoreAndSampling()
741
851
  if (!deps || deps.HTTP_CLIENT_MODE === 'off') return
package/register.js CHANGED
@@ -445,16 +445,24 @@ try {
445
445
  const { randomBytes } = require('crypto')
446
446
  const { performance } = require('perf_hooks')
447
447
  const { getRequestContext, getLastJobContext, toTraceIdHex } = require('./store')
448
+ const {
449
+ resolveUndiciRequestOpts,
450
+ serializeUndiciRequestBody,
451
+ restoreUndiciResponseBodyAfterReadForLog
452
+ } = require('./utils/undiciLogBodies')
448
453
  const origRequest = exports.request
449
454
  exports.request = function(url, options, callback) {
450
455
  const ctx = getRequestContext() || getLastJobContext()
451
456
  const otelCtx = getOtelTraceContext()
452
457
  const traceCtx = ctx || otelCtx
453
- const method = (options?.method || 'GET').toUpperCase()
458
+ const reqOpts = resolveUndiciRequestOpts(url, options)
459
+ const method = (reqOpts.method || options?.method || 'GET').toUpperCase()
454
460
  const urlString = typeof url === 'string' ? url : (url && url.toString && url.toString()) || 'unknown'
455
461
  const rawTraceId = traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
456
462
  const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
463
+ const requestBodyStr = serializeUndiciRequestBody(reqOpts)
457
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
458
466
  markSource(requestMeta, 'http-client')
459
467
  const startTime = performance.now()
460
468
  if (HTTP_CLIENT_MODE !== 'off') {
@@ -462,20 +470,39 @@ try {
462
470
  }
463
471
  const wrappedCb = callback ? function(err, data) {
464
472
  const duration = Number((performance.now() - startTime).toFixed(2))
465
- if (HTTP_CLIENT_MODE !== 'off') {
466
- if (err) sendOutboundLog('error', `[RESPONSE] ${method} ${urlString} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
467
- 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)
468
478
  }
469
- 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
+ })
470
493
  } : undefined
471
494
  if (wrappedCb) return origRequest.call(this, url, options, wrappedCb)
472
495
  const promise = origRequest.call(this, url, options)
473
496
  if (promise && typeof promise.then === 'function') {
474
497
  return promise.then(
475
- (data) => {
498
+ async (data) => {
476
499
  const duration = Number((performance.now() - startTime).toFixed(2))
477
500
  if (HTTP_CLIENT_MODE !== 'off' && data) {
478
- 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
479
506
  }
480
507
  return data
481
508
  },
@@ -504,7 +531,9 @@ try {
504
531
  const urlString = origin ? (origin + (path.startsWith('/') ? path : '/' + path)) : 'unknown'
505
532
  const rawTraceId = traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
506
533
  const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
534
+ const requestBodyStr = serializeUndiciRequestBody(opts || {})
507
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
508
537
  markSource(requestMeta, 'http-client')
509
538
  const startTime = performance.now()
510
539
  if (HTTP_CLIENT_MODE !== 'off') {
@@ -512,19 +541,38 @@ try {
512
541
  }
513
542
  const wrappedCb = typeof callback === 'function' ? function(err, data) {
514
543
  const duration = Number((performance.now() - startTime).toFixed(2))
515
- if (HTTP_CLIENT_MODE !== 'off') {
516
- if (err) sendOutboundLog('error', `[RESPONSE] ${methodStr} ${urlString} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
517
- 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)
518
549
  }
519
- 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
+ })
520
564
  } : undefined
521
565
  const ret = wrappedCb ? origDispRequest.call(this, opts, wrappedCb) : origDispRequest.call(this, opts)
522
566
  if (ret && typeof ret.then === 'function' && !wrappedCb) {
523
567
  return ret.then(
524
- (data) => {
568
+ async (data) => {
525
569
  const duration = Number((performance.now() - startTime).toFixed(2))
526
570
  if (HTTP_CLIENT_MODE !== 'off' && data) {
527
- 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
528
576
  }
529
577
  return data
530
578
  },
@@ -1588,6 +1636,35 @@ try {
1588
1636
  return url
1589
1637
  }
1590
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
+
1591
1668
  const patchInstance = (instance) => {
1592
1669
  if (instance.__azifyLoggerPatched) {
1593
1670
  return instance
@@ -1631,6 +1708,11 @@ try {
1631
1708
  headers: config.headers
1632
1709
  }
1633
1710
 
1711
+ const requestBodyString = stringifyHttpBodyForLog(config.data)
1712
+ if (requestBodyString != null) {
1713
+ requestMeta.requestBody = requestBodyString
1714
+ }
1715
+
1634
1716
  markSource(requestMeta, 'http-client')
1635
1717
  config.__azifyLogger = {
1636
1718
  meta: requestMeta,
@@ -1684,12 +1766,15 @@ try {
1684
1766
 
1685
1767
  const stringifyBody = (value) => {
1686
1768
  if (value == null) return null
1687
- 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
+ }
1688
1773
  try {
1689
- return JSON.stringify(value)
1774
+ return clipHttpLogString(JSON.stringify(value))
1690
1775
  } catch (_) {
1691
1776
  try {
1692
- return String(value)
1777
+ return clipHttpLogString(String(value))
1693
1778
  } catch (_) {
1694
1779
  return null
1695
1780
  }
@@ -1698,17 +1783,16 @@ try {
1698
1783
 
1699
1784
  if (marker && marker.meta) {
1700
1785
  duration = Number((performance.now() - marker.start).toFixed(2))
1701
- let responseBodyString = stringifyBody(response.data)
1702
- if (typeof responseBodyString === 'string' && responseBodyString.length > 5000) {
1703
- responseBodyString = responseBodyString.slice(0, 5000)
1704
- }
1786
+ const responseBodyString = stringifyBody(response.data)
1705
1787
  meta = {
1706
1788
  ...marker.meta,
1707
1789
  url: finalUrl,
1708
1790
  statusCode: response.status,
1709
1791
  responseTimeMs: duration,
1710
- responseHeaders: response.headers,
1711
- responseBody: responseBodyString
1792
+ responseHeaders: response.headers
1793
+ }
1794
+ if (responseBodyString != null) {
1795
+ meta.responseBody = responseBodyString
1712
1796
  }
1713
1797
  } else {
1714
1798
  const requestHeaders = response.config?.headers || {}
@@ -1717,10 +1801,7 @@ try {
1717
1801
  const spanId = requestHeaders['x-span-id'] || requestHeaders['X-Span-ID'] || randomBytes(8).toString('hex')
1718
1802
  const parentSpanId = requestHeaders['x-parent-span-id'] || requestHeaders['X-Parent-Span-ID'] || ctx?.spanId || null
1719
1803
  const requestId = requestHeaders['x-request-id'] || requestHeaders['X-Request-ID'] || ctx?.requestId || randomUUID()
1720
- let responseBodyString = stringifyBody(response.data)
1721
- if (typeof responseBodyString === 'string' && responseBodyString.length > 5000) {
1722
- responseBodyString = responseBodyString.slice(0, 5000)
1723
- }
1804
+ const responseBodyString = stringifyBody(response.data)
1724
1805
 
1725
1806
  meta = {
1726
1807
  traceId,
@@ -1731,8 +1812,10 @@ try {
1731
1812
  url: finalUrl,
1732
1813
  statusCode: response.status,
1733
1814
  responseTimeMs: duration,
1734
- responseHeaders: response.headers,
1735
- responseBody: responseBodyString
1815
+ responseHeaders: response.headers
1816
+ }
1817
+ if (responseBodyString != null) {
1818
+ meta.responseBody = responseBodyString
1736
1819
  }
1737
1820
  }
1738
1821
 
@@ -1931,19 +2014,35 @@ try {
1931
2014
  } catch (_) {
1932
2015
  }
1933
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
+
1934
2026
  let requestBodyString = null
1935
- if (init && init.body !== null && init.body !== undefined) {
1936
- try {
2027
+ try {
2028
+ if (init && init.body !== null && init.body !== undefined) {
1937
2029
  if (typeof init.body === 'string') {
1938
- requestBodyString = init.body
2030
+ requestBodyString = clipFetchBody(init.body)
1939
2031
  } else if (init.body instanceof FormData || init.body instanceof URLSearchParams) {
1940
2032
  requestBodyString = '[FormData/URLSearchParams]'
1941
- } else if (typeof init.body === 'object' && !(init.body instanceof Blob) && !(init.body instanceof ArrayBuffer)) {
1942
- try {
1943
- requestBodyString = JSON.stringify(init.body)
1944
- } catch (_) {
1945
- requestBodyString = String(init.body)
1946
- }
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)
1947
2046
  }
1948
2047
  } catch (_) {
1949
2048
  }
@@ -2050,10 +2149,15 @@ try {
2050
2149
  }
2051
2150
  }
2052
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
+
2053
2157
  if (contentType.includes('application/json') || contentType.includes('text/')) {
2054
2158
  try {
2055
2159
  const clonedResponse = response.clone()
2056
- const bodyText = await readBodyWithTimeout(clonedResponse, 200)
2160
+ const bodyText = await readBodyWithTimeout(clonedResponse, responseReadMs)
2057
2161
  if (bodyText) {
2058
2162
  if (contentType.includes('application/json')) {
2059
2163
  try {
@@ -2064,13 +2168,13 @@ try {
2064
2168
  } else {
2065
2169
  responseBodyString = bodyText
2066
2170
  }
2067
- if (typeof responseBodyString === 'string' && responseBodyString.length > 5000) {
2068
- responseBodyString = responseBodyString.slice(0, 5000)
2171
+ if (typeof responseBodyString === 'string' && responseBodyString.length > maxFetchBodyChars) {
2172
+ responseBodyString = responseBodyString.slice(0, maxFetchBodyChars)
2069
2173
  }
2070
2174
  }
2071
2175
  } catch (_) {
2072
2176
  try {
2073
- const bodyText = await readBodyWithTimeout(response, 200)
2177
+ const bodyText = await readBodyWithTimeout(response, responseReadMs)
2074
2178
  if (bodyText) {
2075
2179
  if (contentType.includes('application/json')) {
2076
2180
  try {
@@ -2081,13 +2185,23 @@ try {
2081
2185
  } else {
2082
2186
  responseBodyString = bodyText
2083
2187
  }
2084
- if (typeof responseBodyString === 'string' && responseBodyString.length > 5000) {
2085
- responseBodyString = responseBodyString.slice(0, 5000)
2188
+ if (typeof responseBodyString === 'string' && responseBodyString.length > maxFetchBodyChars) {
2189
+ responseBodyString = responseBodyString.slice(0, maxFetchBodyChars)
2086
2190
  }
2087
2191
  }
2088
2192
  } catch (_) {
2089
2193
  }
2090
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
+ }
2091
2205
  }
2092
2206
  } catch (_) {
2093
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
@@ -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
+ }