azify-logger 1.0.44 → 1.0.45

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/sampling.js CHANGED
@@ -45,7 +45,7 @@ const sourceSampleRates = {
45
45
  }
46
46
 
47
47
  const shouldSample = (level, source) => {
48
- if (source === 'http-client') {
48
+ if (source === 'http-client' || source === 'pino-stdout') {
49
49
  return true
50
50
  }
51
51
  const key = typeof level === 'string' ? level.toLowerCase() : 'info'
package/server.js CHANGED
@@ -227,6 +227,7 @@ async function ensureIndexTemplate() {
227
227
  userAgent: { type: 'text' },
228
228
  environment: { type: 'keyword' },
229
229
  hostname: { type: 'keyword' },
230
+ source: { type: 'keyword' },
230
231
  responseBody: { type: 'text' },
231
232
  error: {
232
233
  properties: {
@@ -238,7 +239,7 @@ async function ensureIndexTemplate() {
238
239
  }
239
240
  }
240
241
  },
241
- priority: 500
242
+ priority: 1000
242
243
  })
243
244
  } catch (error) {
244
245
  }
@@ -1836,21 +1837,55 @@ async function handleLog(req, res) {
1836
1837
  ))
1837
1838
  )
1838
1839
 
1839
- if (shouldFilterLog) {
1840
+ const isRequestOrResponse = messageLower.includes('[request]') || messageLower.includes('[response]')
1841
+ const isAzifyHttpClientLog = (meta && meta.source === 'http-client') || isRequestOrResponse
1842
+ if (!isAzifyHttpClientLog && shouldFilterLog) {
1840
1843
  return res.json({ success: true, message: 'Log filtrado' })
1841
1844
  }
1845
+ if (isRequestOrResponse && process.env.AZIFY_LOGGER_DEBUG === '1') {
1846
+ console.log('[azify-logger][server] REQUEST/RESPONSE received:', (typeof message === 'string' ? message : '').slice(0, 80))
1847
+ }
1848
+ if (isRequestOrResponse && meta && typeof meta === 'object' && meta.source !== 'http-client') {
1849
+ meta = { ...meta, source: 'http-client' }
1850
+ }
1842
1851
 
1843
1852
  const requestId = meta && meta.requestId
1844
1853
 
1854
+ function toTraceIdHex32(val) {
1855
+ if (val == null || typeof val !== 'string') return null
1856
+ const hex = String(val).replace(/[^0-9a-fA-F]/g, '').slice(0, 32)
1857
+ return hex ? hex.padStart(32, '0').slice(0, 32) : null
1858
+ }
1859
+
1845
1860
  let traceContext = null
1846
1861
 
1847
1862
  if (meta && meta.traceId && meta.spanId) {
1848
- traceContext = {
1849
- traceId: meta.traceId,
1850
- spanId: meta.spanId,
1851
- parentSpanId: meta.parentSpanId || null
1863
+ const tid = toTraceIdHex32(meta.traceId)
1864
+ if (tid) {
1865
+ traceContext = {
1866
+ traceId: tid,
1867
+ spanId: String(meta.spanId).slice(0, 16),
1868
+ parentSpanId: meta.parentSpanId != null && meta.parentSpanId !== '' ? String(meta.parentSpanId) : null
1869
+ }
1852
1870
  }
1853
- } else {
1871
+ }
1872
+
1873
+ if (!traceContext && typeof message === 'string' && /traceId/i.test(message)) {
1874
+ const m = message.match(/"traceId"\s*:\s*"([^"]+)"/)
1875
+ const s = message.match(/"spanId"\s*:\s*"([^"]+)"/)
1876
+ if (m && m[1]) {
1877
+ const tid = toTraceIdHex32(m[1])
1878
+ if (tid) {
1879
+ traceContext = {
1880
+ traceId: tid,
1881
+ spanId: (s && s[1]) ? String(s[1]).slice(0, 16) : generateSpanId(),
1882
+ parentSpanId: null
1883
+ }
1884
+ }
1885
+ }
1886
+ }
1887
+
1888
+ if (!traceContext) {
1854
1889
  try {
1855
1890
  const extractedCtx = propagation.extract(context.active(), req.headers, {
1856
1891
  get (carrier, key) {
@@ -1867,8 +1902,8 @@ async function handleLog(req, res) {
1867
1902
  const spanContext = (span && span.spanContext && span.spanContext()) || null
1868
1903
  if (spanContext && spanContext.traceId && spanContext.spanId) {
1869
1904
  traceContext = {
1870
- traceId: spanContext.traceId,
1871
- spanId: spanContext.spanId,
1905
+ traceId: toTraceIdHex32(spanContext.traceId) || String(spanContext.traceId).replace(/-/g, '').padStart(32, '0').slice(0, 32),
1906
+ spanId: String(spanContext.spanId).slice(0, 16),
1872
1907
  parentSpanId: null
1873
1908
  }
1874
1909
  }
@@ -1991,6 +2026,16 @@ async function handleLog(req, res) {
1991
2026
  value = truncateBody(value, false)
1992
2027
  }
1993
2028
  logEntry[key] = value
2029
+ } else if (key === 'error') {
2030
+ if (value != null && typeof value === 'object' && !Array.isArray(value) && (value.message != null || value.stack != null || value.name != null)) {
2031
+ logEntry.error = {
2032
+ message: value.message != null ? String(value.message) : '',
2033
+ stack: value.stack != null ? String(value.stack) : undefined,
2034
+ name: value.name != null ? String(value.name) : undefined
2035
+ }
2036
+ } else if (value != null) {
2037
+ logEntry.error = { message: String(value) }
2038
+ }
1994
2039
  } else {
1995
2040
  logEntry[key] = value
1996
2041
  }
@@ -1999,6 +2044,7 @@ async function handleLog(req, res) {
1999
2044
  }
2000
2045
 
2001
2046
  logEntry.message = message
2047
+ if (isRequestOrResponse) logEntry.source = 'http-client'
2002
2048
 
2003
2049
  const osUrl = process.env.OPENSEARCH_URL || 'http://localhost:9200'
2004
2050
  const appName = logEntry.appName || logEntry.service?.name || 'unknown'
@@ -2029,8 +2075,17 @@ const _logDedupeBucketMs = 80
2029
2075
 
2030
2076
  function _logDedupeKey(serviceName, logEntry) {
2031
2077
  const msg = typeof logEntry.message === 'string' ? logEntry.message.substring(0, 500) : String(logEntry.message || '')
2078
+ const msgLower = msg.toLowerCase()
2079
+ const isReqRes = msgLower.includes('[request]') || msgLower.includes('[response]')
2032
2080
  const traceId = logEntry.traceId != null ? String(logEntry.traceId).trim() : ''
2033
2081
  const spanId = logEntry.spanId != null ? String(logEntry.spanId).trim() : ''
2082
+ if (isReqRes) {
2083
+ const kind = msgLower.includes('[request]') ? 'REQUEST' : 'RESPONSE'
2084
+ if (traceId && spanId) return `r|${serviceName}|${traceId}|${spanId}|${kind}`
2085
+ const ts = logEntry['@timestamp'] != null ? Number(logEntry['@timestamp']) : Date.now()
2086
+ const bucket = Math.floor(ts / _logDedupeBucketMs) * _logDedupeBucketMs
2087
+ return `r|${serviceName}|${bucket}|${kind}`
2088
+ }
2034
2089
  if (traceId && spanId) return `t|${serviceName}|${traceId}|${spanId}|${msg}`
2035
2090
  const requestId = (logEntry.requestId != null ? String(logEntry.requestId).trim() : '') || ''
2036
2091
  if (!requestId) return null
@@ -2059,6 +2114,12 @@ function _enqueueOpenSearchWrite(osUrl, indexName, logEntry, serviceName) {
2059
2114
  if (expiry != null && expiry > now) return
2060
2115
  _logDedupeSeen.set(key, now + _logDedupeWindowMs)
2061
2116
  }
2117
+ if (process.env.AZIFY_LOGGER_DEBUG === '1') {
2118
+ const msg = typeof logEntry.message === 'string' ? logEntry.message : ''
2119
+ if (msg.toLowerCase().includes('[request]') || msg.toLowerCase().includes('[response]')) {
2120
+ console.log('[azify-logger][server] REQUEST/RESPONSE enqueued for OpenSearch:', msg.slice(0, 70))
2121
+ }
2122
+ }
2062
2123
  _openSearchWriteQueue.push({ osUrl, indexName, logEntry, serviceName })
2063
2124
  _drainOpenSearchQueue()
2064
2125
  }
@@ -2081,11 +2142,17 @@ function _drainOpenSearchQueue() {
2081
2142
  })
2082
2143
  .then(() => {
2083
2144
  _openSearchFailuresInRow = 0
2145
+ if (process.env.AZIFY_LOGGER_DEBUG === '1') {
2146
+ const msg = typeof job.logEntry.message === 'string' ? job.logEntry.message : ''
2147
+ if (msg.toLowerCase().includes('[request]') || msg.toLowerCase().includes('[response]')) {
2148
+ console.log('[azify-logger][server] REQUEST/RESPONSE written to OpenSearch index=', job.indexName, 'msg=', msg.slice(0, 60))
2149
+ }
2150
+ }
2084
2151
  const SETUP_THROTTLE_MS = 10 * 60 * 1000
2085
2152
  if (!handleLog._lastSetupTime) handleLog._lastSetupTime = new Map()
2086
2153
  const last = handleLog._lastSetupTime.get(job.serviceName) || 0
2087
2154
  const now = Date.now()
2088
- const shouldRunSetup = job.serviceName !== 'unknown' && job.serviceName !== 'unknown-service' && (now - last >= SETUP_THROTTLE_MS)
2155
+ const shouldRunSetup = job.serviceName !== 'unknown' && job.serviceName !== 'unknown-service' && job.serviceName !== 'unknown-app' && (now - last >= SETUP_THROTTLE_MS)
2089
2156
  if (shouldRunSetup) {
2090
2157
  handleLog._lastSetupTime.set(job.serviceName, now)
2091
2158
  setupGrafanaForApp(job.serviceName).then(() => {
package/store.js CHANGED
@@ -1,6 +1,15 @@
1
1
  const { AsyncLocalStorage } = require('async_hooks')
2
2
 
3
3
  const als = new AsyncLocalStorage()
4
+ let lastJobContext = null
5
+
6
+ function setLastJobContext(ctx) {
7
+ lastJobContext = ctx || null
8
+ }
9
+
10
+ function getLastJobContext() {
11
+ return lastJobContext
12
+ }
4
13
 
5
14
  function fastGenerateId(length = 16) {
6
15
  const chars = '0123456789abcdef'
@@ -12,21 +21,22 @@ function fastGenerateId(length = 16) {
12
21
  return result
13
22
  }
14
23
 
15
- function toTraceId(hex32) {
16
- const h = (hex32 || '').padStart(32, '0').slice(0, 32)
17
- return `${h.substring(0, 8)}-${h.substring(8, 12)}-${h.substring(12, 16)}-${h.substring(16, 20)}-${h.substring(20, 32)}`
24
+ function toTraceIdHex(hex32) {
25
+ const raw = hex32 != null && typeof hex32 === 'string' ? String(hex32).replace(/[^0-9a-fA-F]/g, '').slice(0, 32) : ''
26
+ const hex = raw || fastGenerateId(16)
27
+ return hex.padStart(32, '0').slice(0, 32)
18
28
  }
19
29
 
20
- function startRequestContext(initial = {}) {
30
+ function startRequestContext(initial) {
31
+ if (!initial || typeof initial !== 'object') initial = {}
21
32
  const traceHex = initial.traceHex || fastGenerateId(16)
22
33
  const spanHex = initial.spanHex || fastGenerateId(8)
23
- const ctx = {
24
- traceId: toTraceId(traceHex),
25
- spanId: spanHex,
26
- parentSpanId: initial.parentSpanId || null,
27
- requestId: initial.requestId
34
+ return {
35
+ traceId: toTraceIdHex(traceHex),
36
+ spanId: spanHex != null && spanHex !== '' ? String(spanHex).slice(0, 16) : fastGenerateId(8),
37
+ parentSpanId: initial.parentSpanId != null && initial.parentSpanId !== '' ? String(initial.parentSpanId) : null,
38
+ requestId: initial.requestId != null && initial.requestId !== '' ? String(initial.requestId) : null
28
39
  }
29
- return ctx
30
40
  }
31
41
 
32
42
  function runWithRequestContext(ctx, fn) {
@@ -37,11 +47,27 @@ function getRequestContext() {
37
47
  return als.getStore() || null
38
48
  }
39
49
 
50
+ function mergeTraceIntoJobPayload(data, traceContext) {
51
+ if (!traceContext || !traceContext.traceId) return data
52
+ const base = data && typeof data === 'object' && !Array.isArray(data) ? data : {}
53
+ return {
54
+ ...base,
55
+ traceId: traceContext.traceId,
56
+ spanId: traceContext.spanId ?? null,
57
+ parentSpanId: traceContext.parentSpanId ?? null,
58
+ requestId: traceContext.requestId ?? null
59
+ }
60
+ }
61
+
40
62
  module.exports = {
41
63
  als,
42
64
  startRequestContext,
43
65
  runWithRequestContext,
44
- getRequestContext
66
+ getRequestContext,
67
+ getLastJobContext,
68
+ setLastJobContext,
69
+ mergeTraceIntoJobPayload,
70
+ toTraceIdHex
45
71
  }
46
72
 
47
73
 
@@ -84,7 +84,7 @@ function resolveRedisConfig (overrides = {}) {
84
84
  (overrides.redis && overrides.redis.url) ||
85
85
  process.env.AZIFY_LOGGER_REDIS_URL ||
86
86
  DEFAULT_REDIS_URL
87
-
87
+
88
88
  if (!redisUrl) {
89
89
  return null
90
90
  }
@@ -108,11 +108,13 @@ function createHttpLoggerTransport (loggerUrl, overrides) {
108
108
  if (!loggerUrl) {
109
109
  return noopTransport
110
110
  }
111
+ overrides = overrides || {}
111
112
 
112
113
  const options = resolveOptions(overrides)
113
114
  const redisConfig = resolveRedisConfig(overrides)
115
+ const hasRedisPassword = redisConfig && redisConfig.password != null && String(redisConfig.password).trim() !== ''
114
116
 
115
- if (redisConfig) {
117
+ if (redisConfig && hasRedisPassword) {
116
118
  try {
117
119
  return createRedisStreamTransport(loggerUrl, options, redisConfig)
118
120
  } catch (err) {
@@ -124,9 +126,7 @@ function createHttpLoggerTransport (loggerUrl, overrides) {
124
126
  }
125
127
 
126
128
  const redisUrl = overrides.redisUrl || (overrides.redis && overrides.redis.url) || process.env.AZIFY_LOGGER_REDIS_URL || DEFAULT_REDIS_URL
127
- const password = overrides.redisPassword ?? (overrides.redis && overrides.redis.password) ?? process.env.AZIFY_LOGGER_REDIS_PASSWORD
128
- const pass = password != null && String(password).trim() !== '' ? String(password).trim() : undefined
129
- if (redisUrl && !pass) {
129
+ if (redisUrl && !hasRedisPassword) {
130
130
  if (typeof global.__azifyLoggerRedisPasswordWarned === 'undefined') {
131
131
  global.__azifyLoggerRedisPasswordWarned = true
132
132
  process.stderr.write('[azify-logger] ⚠️ Redis requires a password. No password set. Using direct HTTP (no Redis). Set AZIFY_LOGGER_REDIS_PASSWORD to use Redis.\n')
@@ -250,7 +250,7 @@ function buildInlineTransport (loggerUrl, options) {
250
250
 
251
251
  flushTimer = setTimeout(() => {
252
252
  flushTimer = null
253
- void flushQueue()
253
+ flushQueue().catch(() => {})
254
254
  }, delay)
255
255
 
256
256
  if (typeof flushTimer.unref === 'function') {
@@ -278,9 +278,15 @@ function buildInlineTransport (loggerUrl, options) {
278
278
  try {
279
279
  for (let i = 0; i < batch.length; i += 1) {
280
280
  const { payload, headers } = batch[i]
281
+ let body
282
+ try {
283
+ body = typeof payload === 'string' ? payload : JSON.stringify(payload)
284
+ } catch (_) {
285
+ continue
286
+ }
281
287
  try {
282
- await axios.post(loggerUrl, payload, {
283
- headers,
288
+ await axios.post(loggerUrl, body, {
289
+ headers: { ...headers, 'Content-Type': 'application/json' },
284
290
  timeout: options.timeout,
285
291
  httpAgent,
286
292
  httpsAgent,
@@ -298,11 +304,9 @@ function buildInlineTransport (loggerUrl, options) {
298
304
  if (remaining.length) {
299
305
  queue.unshift(...remaining)
300
306
  }
301
-
302
- throw error
303
307
  }
304
308
  }
305
- } catch (_) {
309
+ } catch (e) {
306
310
  } finally {
307
311
  flushing = false
308
312
  if (queue.length) {
@@ -311,6 +315,12 @@ function buildInlineTransport (loggerUrl, options) {
311
315
  }
312
316
  }
313
317
 
318
+ const isRequestOrResponsePayload = (p) => {
319
+ if (!p || typeof p !== 'object') return false
320
+ const msg = typeof p.message === 'string' ? p.message : ''
321
+ return msg.includes('[REQUEST]') || msg.includes('[RESPONSE]')
322
+ }
323
+
314
324
  const enqueue = (payload, headers = {}) => {
315
325
  if (queue.length >= options.maxQueueSize) {
316
326
  queue.shift()
@@ -319,7 +329,9 @@ function buildInlineTransport (loggerUrl, options) {
319
329
  queue.push({ payload, headers })
320
330
 
321
331
  if (queue.length >= options.batchSize) {
322
- void flushQueue()
332
+ flushQueue().catch(() => {})
333
+ } else if (isRequestOrResponsePayload(payload)) {
334
+ scheduleFlush(0)
323
335
  } else {
324
336
  scheduleFlush()
325
337
  }
package/streams/pino.js CHANGED
@@ -36,26 +36,49 @@ function createPinoStream(options = {}) {
36
36
 
37
37
  let traceId, spanId, parentSpanId
38
38
  try {
39
- const { getRequestContext } = require('../store')
39
+ const { getRequestContext, getLastJobContext, toTraceIdHex } = require('../store')
40
40
  const ctx = getRequestContext()
41
41
  if (ctx && ctx.traceId) {
42
42
  traceId = ctx.traceId
43
43
  spanId = ctx.spanId
44
44
  parentSpanId = ctx.parentSpanId
45
45
  }
46
-
47
- if (!traceId && record.traceId) {
48
- traceId = record.traceId
49
- spanId = record.spanId
50
- parentSpanId = record.parentSpanId
46
+
47
+ if (!traceId) {
48
+ const jobCtx = getLastJobContext()
49
+ if (jobCtx && jobCtx.traceId) {
50
+ traceId = jobCtx.traceId
51
+ spanId = jobCtx.spanId
52
+ parentSpanId = jobCtx.parentSpanId
53
+ }
54
+ }
55
+
56
+ if (!traceId && (record.traceId || (record.data && record.data.traceId))) {
57
+ traceId = record.traceId || (record.data && record.data.traceId)
58
+ spanId = record.spanId != null ? record.spanId : (record.data && record.data.spanId)
59
+ parentSpanId = record.parentSpanId != null ? record.parentSpanId : (record.data && record.data.parentSpanId)
51
60
  }
52
-
61
+
53
62
  if (!traceId && record.req && record.req.traceId) {
54
63
  traceId = record.req.traceId
55
64
  spanId = record.req.spanId
56
65
  parentSpanId = record.req.parentSpanId
57
66
  }
58
-
67
+
68
+ if (!traceId) {
69
+ const msgStr = record.msg || record.message || ''
70
+ if (typeof msgStr === 'string' && /traceId/i.test(msgStr)) {
71
+ const m = msgStr.match(/"traceId"\s*:\s*"([^"]+)"/)
72
+ if (m) {
73
+ traceId = m[1]
74
+ const s = msgStr.match(/"spanId"\s*:\s*"([^"]+)"/)
75
+ spanId = s ? s[1] : null
76
+ const p = msgStr.match(/"parentSpanId"\s*:\s*"([^"]*)"/)
77
+ parentSpanId = (p && p[1]) ? p[1] : null
78
+ }
79
+ }
80
+ }
81
+
59
82
  if (!traceId) {
60
83
  const span = trace.getSpan(context.active())
61
84
  const spanContext = span && span.spanContext()
@@ -65,6 +88,11 @@ function createPinoStream(options = {}) {
65
88
  parentSpanId = spanContext.parentSpanId
66
89
  }
67
90
  }
91
+
92
+ if (traceId && typeof traceId === 'string') {
93
+ traceId = toTraceIdHex(traceId)
94
+ if (spanId != null) spanId = String(spanId).slice(0, 16)
95
+ }
68
96
  } catch (_) {}
69
97
 
70
98
  const headers = {}
@@ -90,12 +118,19 @@ function createPinoStream(options = {}) {
90
118
  ...(parentSpanId && { parentSpanId })
91
119
  }
92
120
 
93
- if (!shouldSample(level, 'logger')) {
121
+ const isErrorOrWarn = level === 'error' || level === 'fatal' || level === 'warn'
122
+ if (!isErrorOrWarn && !shouldSample(level, 'logger')) {
94
123
  return
95
124
  }
96
125
 
97
126
  const payload = { level, message: record.msg || record.message || 'log', meta }
98
- transport.enqueue(payload, headers)
127
+ try {
128
+ transport.enqueue(payload, headers)
129
+ } catch (err) {
130
+ if (process.env.AZIFY_LOGGER_DEBUG === '1') {
131
+ process.stderr.write(`[azify-logger] pino stream enqueue error: ${err && err.message}\n`)
132
+ }
133
+ }
99
134
  }
100
135
  }
101
136
  }