azify-logger 1.0.55 → 1.0.57

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/register.js CHANGED
@@ -25,6 +25,7 @@ try {
25
25
  const os = require('os')
26
26
 
27
27
  const { shouldSample, markSource, HTTP_CLIENT_MODE } = require('./sampling')
28
+ const { sanitizeOutboundHttpMetaBodies } = require('./utils/sanitizeSensitiveHttpBody')
28
29
 
29
30
  if (process.env.AZIFY_LOGGER_DEBUG === '1') {
30
31
  try { process.stderr.write('[azify-logger] REGISTER step1b after requires\n') } catch (_) {}
@@ -32,13 +33,17 @@ try {
32
33
  }
33
34
  const serviceName = process.env.APP_NAME
34
35
  const environment = process.env.NODE_ENV
35
- const loggerUrlFromEnv = process.env.AZIFY_LOGGER_URL
36
-
36
+ const loggerUrlRaw = (process.env.AZIFY_LOGGER_URL || '').trim()
37
+ if (!loggerUrlRaw) {
38
+ process.stderr.write('[azify-logger] AZIFY_LOGGER_URL não está definida.\n')
39
+ throw new Error('AZIFY_LOGGER_URL is required')
40
+ }
37
41
  let loggerEndpoint
38
42
  try {
39
- loggerEndpoint = new URL(loggerUrlFromEnv)
40
- } catch (_) {
41
- loggerEndpoint = new URL('http://localhost:3001/log')
43
+ loggerEndpoint = new URL(loggerUrlRaw)
44
+ } catch (urlErr) {
45
+ process.stderr.write(`[azify-logger] AZIFY_LOGGER_URL inválida: ${loggerUrlRaw} (${urlErr.message})\n`)
46
+ throw urlErr
42
47
  }
43
48
 
44
49
  const loggerUrlString = loggerEndpoint.toString()
@@ -176,6 +181,9 @@ try {
176
181
  const fromMsg = extractUrlFromReqResMessage(msgStr)
177
182
  meta = fromMsg ? { ...meta, url: fromMsg } : { ...meta, url: OPAQUE_NO_URL }
178
183
  }
184
+ try {
185
+ meta = sanitizeOutboundHttpMetaBodies(meta)
186
+ } catch (_) {}
179
187
  }
180
188
  if (isReqRes && meta && meta.url && isLoggerApiCall({ url: meta.url })) return
181
189
  const source = meta && meta.__source
@@ -445,16 +453,24 @@ try {
445
453
  const { randomBytes } = require('crypto')
446
454
  const { performance } = require('perf_hooks')
447
455
  const { getRequestContext, getLastJobContext, toTraceIdHex } = require('./store')
456
+ const {
457
+ resolveUndiciRequestOpts,
458
+ serializeUndiciRequestBody,
459
+ restoreUndiciResponseBodyAfterReadForLog
460
+ } = require('./utils/undiciLogBodies')
448
461
  const origRequest = exports.request
449
462
  exports.request = function(url, options, callback) {
450
463
  const ctx = getRequestContext() || getLastJobContext()
451
464
  const otelCtx = getOtelTraceContext()
452
465
  const traceCtx = ctx || otelCtx
453
- const method = (options?.method || 'GET').toUpperCase()
466
+ const reqOpts = resolveUndiciRequestOpts(url, options)
467
+ const method = (reqOpts.method || options?.method || 'GET').toUpperCase()
454
468
  const urlString = typeof url === 'string' ? url : (url && url.toString && url.toString()) || 'unknown'
455
469
  const rawTraceId = traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
456
470
  const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
471
+ const requestBodyStr = serializeUndiciRequestBody(reqOpts)
457
472
  const requestMeta = { traceId, spanId: traceCtx?.spanId || null, parentSpanId: traceCtx?.parentSpanId || null, requestId: traceCtx?.requestId || null, method, url: urlString }
473
+ if (requestBodyStr != null) requestMeta.requestBody = requestBodyStr
458
474
  markSource(requestMeta, 'http-client')
459
475
  const startTime = performance.now()
460
476
  if (HTTP_CLIENT_MODE !== 'off') {
@@ -462,20 +478,39 @@ try {
462
478
  }
463
479
  const wrappedCb = callback ? function(err, data) {
464
480
  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 })
481
+ if (err) {
482
+ if (HTTP_CLIENT_MODE !== 'off') {
483
+ sendOutboundLog('error', `[RESPONSE] ${method} ${urlString} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
484
+ }
485
+ return callback(err, data)
468
486
  }
469
- return callback(err, data)
487
+ if (!data) return callback(err, data)
488
+ restoreUndiciResponseBodyAfterReadForLog(data).then(({ logStr, data: restored }) => {
489
+ if (HTTP_CLIENT_MODE !== 'off') {
490
+ const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
491
+ if (logStr != null) meta.responseBody = logStr
492
+ sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${restored.statusCode} ${duration}ms`, meta)
493
+ }
494
+ return callback(null, restored)
495
+ }).catch(() => {
496
+ if (HTTP_CLIENT_MODE !== 'off') {
497
+ sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
498
+ }
499
+ return callback(null, data)
500
+ })
470
501
  } : undefined
471
502
  if (wrappedCb) return origRequest.call(this, url, options, wrappedCb)
472
503
  const promise = origRequest.call(this, url, options)
473
504
  if (promise && typeof promise.then === 'function') {
474
505
  return promise.then(
475
- (data) => {
506
+ async (data) => {
476
507
  const duration = Number((performance.now() - startTime).toFixed(2))
477
508
  if (HTTP_CLIENT_MODE !== 'off' && data) {
478
- sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
509
+ const { logStr, data: restored } = await restoreUndiciResponseBodyAfterReadForLog(data)
510
+ const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
511
+ if (logStr != null) meta.responseBody = logStr
512
+ sendOutboundLog('info', `[RESPONSE] ${method} ${urlString} ${restored.statusCode} ${duration}ms`, meta)
513
+ return restored
479
514
  }
480
515
  return data
481
516
  },
@@ -504,7 +539,9 @@ try {
504
539
  const urlString = origin ? (origin + (path.startsWith('/') ? path : '/' + path)) : 'unknown'
505
540
  const rawTraceId = traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
506
541
  const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
542
+ const requestBodyStr = serializeUndiciRequestBody(opts || {})
507
543
  const requestMeta = { traceId, spanId: traceCtx?.spanId || null, parentSpanId: traceCtx?.parentSpanId || null, requestId: traceCtx?.requestId || null, method: methodStr, url: urlString }
544
+ if (requestBodyStr != null) requestMeta.requestBody = requestBodyStr
508
545
  markSource(requestMeta, 'http-client')
509
546
  const startTime = performance.now()
510
547
  if (HTTP_CLIENT_MODE !== 'off') {
@@ -512,19 +549,38 @@ try {
512
549
  }
513
550
  const wrappedCb = typeof callback === 'function' ? function(err, data) {
514
551
  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 })
552
+ if (err) {
553
+ if (HTTP_CLIENT_MODE !== 'off') {
554
+ sendOutboundLog('error', `[RESPONSE] ${methodStr} ${urlString} ERROR ${duration}ms`, { ...requestMeta, error: err && err.message, responseTimeMs: duration })
555
+ }
556
+ return callback(err, data)
518
557
  }
519
- return callback(err, data)
558
+ if (!data) return callback(err, data)
559
+ restoreUndiciResponseBodyAfterReadForLog(data).then(({ logStr, data: restored }) => {
560
+ if (HTTP_CLIENT_MODE !== 'off') {
561
+ const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
562
+ if (logStr != null) meta.responseBody = logStr
563
+ sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${restored.statusCode} ${duration}ms`, meta)
564
+ }
565
+ return callback(null, restored)
566
+ }).catch(() => {
567
+ if (HTTP_CLIENT_MODE !== 'off') {
568
+ sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
569
+ }
570
+ return callback(null, data)
571
+ })
520
572
  } : undefined
521
573
  const ret = wrappedCb ? origDispRequest.call(this, opts, wrappedCb) : origDispRequest.call(this, opts)
522
574
  if (ret && typeof ret.then === 'function' && !wrappedCb) {
523
575
  return ret.then(
524
- (data) => {
576
+ async (data) => {
525
577
  const duration = Number((performance.now() - startTime).toFixed(2))
526
578
  if (HTTP_CLIENT_MODE !== 'off' && data) {
527
- sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${data.statusCode} ${duration}ms`, { ...requestMeta, statusCode: data.statusCode, responseTimeMs: duration })
579
+ const { logStr, data: restored } = await restoreUndiciResponseBodyAfterReadForLog(data)
580
+ const meta = { ...requestMeta, statusCode: restored.statusCode, responseTimeMs: duration }
581
+ if (logStr != null) meta.responseBody = logStr
582
+ sendOutboundLog('info', `[RESPONSE] ${methodStr} ${urlString} ${restored.statusCode} ${duration}ms`, meta)
583
+ return restored
528
584
  }
529
585
  return data
530
586
  },
@@ -1588,6 +1644,35 @@ try {
1588
1644
  return url
1589
1645
  }
1590
1646
 
1647
+ const MAX_HTTP_BODY_CHARS = Math.min(
1648
+ Math.max(parseInt(String(process.env.AZIFY_LOGGER_HTTP_BODY_MAX_CHARS || '5000'), 10) || 5000, 100),
1649
+ 100000
1650
+ )
1651
+ function clipHttpLogString(s) {
1652
+ if (typeof s !== 'string') return s
1653
+ return s.length > MAX_HTTP_BODY_CHARS ? s.slice(0, MAX_HTTP_BODY_CHARS) : s
1654
+ }
1655
+ function stringifyHttpBodyForLog(value) {
1656
+ if (value == null) return null
1657
+ try {
1658
+ if (typeof value === 'string') {
1659
+ return clipHttpLogString(value)
1660
+ }
1661
+ if (Buffer.isBuffer(value)) {
1662
+ return clipHttpLogString(value.toString('utf8'))
1663
+ }
1664
+ if (value && typeof value.pipe === 'function') {
1665
+ return '[Stream]'
1666
+ }
1667
+ if (typeof value === 'object') {
1668
+ return clipHttpLogString(JSON.stringify(value))
1669
+ }
1670
+ return clipHttpLogString(String(value))
1671
+ } catch (_) {
1672
+ return '[unserializable]'
1673
+ }
1674
+ }
1675
+
1591
1676
  const patchInstance = (instance) => {
1592
1677
  if (instance.__azifyLoggerPatched) {
1593
1678
  return instance
@@ -1631,6 +1716,11 @@ try {
1631
1716
  headers: config.headers
1632
1717
  }
1633
1718
 
1719
+ const requestBodyString = stringifyHttpBodyForLog(config.data)
1720
+ if (requestBodyString != null) {
1721
+ requestMeta.requestBody = requestBodyString
1722
+ }
1723
+
1634
1724
  markSource(requestMeta, 'http-client')
1635
1725
  config.__azifyLogger = {
1636
1726
  meta: requestMeta,
@@ -1684,12 +1774,15 @@ try {
1684
1774
 
1685
1775
  const stringifyBody = (value) => {
1686
1776
  if (value == null) return null
1687
- if (typeof value === 'string') return value
1777
+ if (typeof value === 'string') return clipHttpLogString(value)
1778
+ if (Buffer.isBuffer(value)) {
1779
+ return clipHttpLogString(value.toString('utf8'))
1780
+ }
1688
1781
  try {
1689
- return JSON.stringify(value)
1782
+ return clipHttpLogString(JSON.stringify(value))
1690
1783
  } catch (_) {
1691
1784
  try {
1692
- return String(value)
1785
+ return clipHttpLogString(String(value))
1693
1786
  } catch (_) {
1694
1787
  return null
1695
1788
  }
@@ -1698,17 +1791,16 @@ try {
1698
1791
 
1699
1792
  if (marker && marker.meta) {
1700
1793
  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
- }
1794
+ const responseBodyString = stringifyBody(response.data)
1705
1795
  meta = {
1706
1796
  ...marker.meta,
1707
1797
  url: finalUrl,
1708
1798
  statusCode: response.status,
1709
1799
  responseTimeMs: duration,
1710
- responseHeaders: response.headers,
1711
- responseBody: responseBodyString
1800
+ responseHeaders: response.headers
1801
+ }
1802
+ if (responseBodyString != null) {
1803
+ meta.responseBody = responseBodyString
1712
1804
  }
1713
1805
  } else {
1714
1806
  const requestHeaders = response.config?.headers || {}
@@ -1717,10 +1809,7 @@ try {
1717
1809
  const spanId = requestHeaders['x-span-id'] || requestHeaders['X-Span-ID'] || randomBytes(8).toString('hex')
1718
1810
  const parentSpanId = requestHeaders['x-parent-span-id'] || requestHeaders['X-Parent-Span-ID'] || ctx?.spanId || null
1719
1811
  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
- }
1812
+ const responseBodyString = stringifyBody(response.data)
1724
1813
 
1725
1814
  meta = {
1726
1815
  traceId,
@@ -1731,8 +1820,10 @@ try {
1731
1820
  url: finalUrl,
1732
1821
  statusCode: response.status,
1733
1822
  responseTimeMs: duration,
1734
- responseHeaders: response.headers,
1735
- responseBody: responseBodyString
1823
+ responseHeaders: response.headers
1824
+ }
1825
+ if (responseBodyString != null) {
1826
+ meta.responseBody = responseBodyString
1736
1827
  }
1737
1828
  }
1738
1829
 
@@ -1931,19 +2022,35 @@ try {
1931
2022
  } catch (_) {
1932
2023
  }
1933
2024
 
2025
+ const maxFetchBodyChars = Math.min(
2026
+ Math.max(parseInt(String(process.env.AZIFY_LOGGER_HTTP_BODY_MAX_CHARS || '5000'), 10) || 5000, 100),
2027
+ 100000
2028
+ )
2029
+ const clipFetchBody = (s) => {
2030
+ if (typeof s !== 'string' || !s.length) return null
2031
+ return s.length > maxFetchBodyChars ? s.slice(0, maxFetchBodyChars) : s
2032
+ }
2033
+
1934
2034
  let requestBodyString = null
1935
- if (init && init.body !== null && init.body !== undefined) {
1936
- try {
2035
+ try {
2036
+ if (init && init.body !== null && init.body !== undefined) {
1937
2037
  if (typeof init.body === 'string') {
1938
- requestBodyString = init.body
2038
+ requestBodyString = clipFetchBody(init.body)
1939
2039
  } else if (init.body instanceof FormData || init.body instanceof URLSearchParams) {
1940
2040
  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
- }
2041
+ }
2042
+ }
2043
+ } catch (_) {
2044
+ }
2045
+ if (requestBodyString == null && request.body != null && method !== 'GET' && method !== 'HEAD') {
2046
+ try {
2047
+ const clone = request.clone()
2048
+ const text = await Promise.race([
2049
+ clone.text(),
2050
+ new Promise((resolve) => setTimeout(() => resolve(null), 4000))
2051
+ ])
2052
+ if (typeof text === 'string' && text.length) {
2053
+ requestBodyString = clipFetchBody(text)
1947
2054
  }
1948
2055
  } catch (_) {
1949
2056
  }
@@ -2050,10 +2157,15 @@ try {
2050
2157
  }
2051
2158
  }
2052
2159
 
2160
+ const responseReadMs = Math.min(
2161
+ Math.max(parseInt(String(process.env.AZIFY_LOGGER_HTTP_RESPONSE_READ_MS || '4000'), 10) || 4000, 200),
2162
+ 30000
2163
+ )
2164
+
2053
2165
  if (contentType.includes('application/json') || contentType.includes('text/')) {
2054
2166
  try {
2055
2167
  const clonedResponse = response.clone()
2056
- const bodyText = await readBodyWithTimeout(clonedResponse, 200)
2168
+ const bodyText = await readBodyWithTimeout(clonedResponse, responseReadMs)
2057
2169
  if (bodyText) {
2058
2170
  if (contentType.includes('application/json')) {
2059
2171
  try {
@@ -2064,13 +2176,13 @@ try {
2064
2176
  } else {
2065
2177
  responseBodyString = bodyText
2066
2178
  }
2067
- if (typeof responseBodyString === 'string' && responseBodyString.length > 5000) {
2068
- responseBodyString = responseBodyString.slice(0, 5000)
2179
+ if (typeof responseBodyString === 'string' && responseBodyString.length > maxFetchBodyChars) {
2180
+ responseBodyString = responseBodyString.slice(0, maxFetchBodyChars)
2069
2181
  }
2070
2182
  }
2071
2183
  } catch (_) {
2072
2184
  try {
2073
- const bodyText = await readBodyWithTimeout(response, 200)
2185
+ const bodyText = await readBodyWithTimeout(response, responseReadMs)
2074
2186
  if (bodyText) {
2075
2187
  if (contentType.includes('application/json')) {
2076
2188
  try {
@@ -2081,13 +2193,23 @@ try {
2081
2193
  } else {
2082
2194
  responseBodyString = bodyText
2083
2195
  }
2084
- if (typeof responseBodyString === 'string' && responseBodyString.length > 5000) {
2085
- responseBodyString = responseBodyString.slice(0, 5000)
2196
+ if (typeof responseBodyString === 'string' && responseBodyString.length > maxFetchBodyChars) {
2197
+ responseBodyString = responseBodyString.slice(0, maxFetchBodyChars)
2086
2198
  }
2087
2199
  }
2088
2200
  } catch (_) {
2089
2201
  }
2090
2202
  }
2203
+ } else if (response.status !== 204 && response.status !== 205 && response.status !== 304) {
2204
+ try {
2205
+ const clonedResponse = response.clone()
2206
+ const bodyText = await readBodyWithTimeout(clonedResponse, responseReadMs)
2207
+ if (bodyText && bodyText.length) {
2208
+ responseBodyString =
2209
+ bodyText.length > maxFetchBodyChars ? bodyText.slice(0, maxFetchBodyChars) : bodyText
2210
+ }
2211
+ } catch (_) {
2212
+ }
2091
2213
  }
2092
2214
  } catch (_) {
2093
2215
  }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ const path = require('path')
3
4
  const os = require('os')
4
5
  const http = require('http')
5
6
  const https = require('https')
@@ -159,24 +160,6 @@ const SENSITIVE_HEADER_KEYS = new Set([
159
160
  'x-timestamp'
160
161
  ])
161
162
 
162
- const SENSITIVE_BODY_FIELDS = new Set([
163
- 'password',
164
- 'token',
165
- 'secret',
166
- 'apiKey',
167
- 'api_key',
168
- 'accessToken',
169
- 'access_token',
170
- 'refreshToken',
171
- 'refresh_token',
172
- 'clientSecret',
173
- 'client_secret',
174
- 'creditCard',
175
- 'credit_card',
176
- 'cvv',
177
- 'cvc'
178
- ])
179
-
180
163
  function sanitizeHeaders(headers) {
181
164
  if (!headers || typeof headers !== 'object') {
182
165
  return {}
@@ -195,31 +178,10 @@ function sanitizeHeaders(headers) {
195
178
  return sanitized
196
179
  }
197
180
 
181
+ const sanitizeBodyJsonValue = require(path.join(__dirname, '..', 'utils', 'sanitizeSensitiveHttpBody')).sanitizeBodyJsonValue
182
+
198
183
  function sanitizeBody(body) {
199
- if (!body || typeof body !== 'object') {
200
- return body
201
- }
202
-
203
- try {
204
- const sanitized = Array.isArray(body) ? [] : {}
205
-
206
- for (const key in body) {
207
- if (!body.hasOwnProperty(key)) continue
208
- const lower = String(key).toLowerCase()
209
-
210
- if (SENSITIVE_BODY_FIELDS.has(lower) || lower.includes('password') || lower.includes('secret')) {
211
- sanitized[key] = '***'
212
- } else if (typeof body[key] === 'object' && body[key] !== null) {
213
- sanitized[key] = sanitizeBody(body[key])
214
- } else {
215
- sanitized[key] = body[key]
216
- }
217
- }
218
-
219
- return sanitized
220
- } catch (err) {
221
- return body
222
- }
184
+ return sanitizeBodyJsonValue(body)
223
185
  }
224
186
 
225
187
  function sanitizePayload(payload) {
@@ -254,6 +216,9 @@ function sanitizePayload(payload) {
254
216
  if (sanitized.meta.responseBody) {
255
217
  sanitized.meta.responseBody = sanitizeBody(sanitized.meta.responseBody)
256
218
  }
219
+ if (sanitized.meta.requestBody) {
220
+ sanitized.meta.requestBody = sanitizeBody(sanitized.meta.requestBody)
221
+ }
257
222
  }
258
223
 
259
224
  return sanitized