azify-logger 1.0.44 → 1.0.46

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
  }
@@ -1696,14 +1697,14 @@ async function registerAlertRulesForApp(appName, rules, options = {}) {
1696
1697
  }
1697
1698
 
1698
1699
  function generateTraceId() {
1699
- return Math.random().toString(36).substring(2, 15) +
1700
- Math.random().toString(36).substring(2, 15)
1700
+ return require('crypto').randomBytes(16).toString('hex')
1701
1701
  }
1702
1702
 
1703
1703
  function generateSpanId() {
1704
- return Math.random().toString(36).substring(2, 15)
1704
+ return require('crypto').randomBytes(8).toString('hex')
1705
1705
  }
1706
1706
 
1707
+
1707
1708
  function getOrCreateTraceContext(requestId) {
1708
1709
  if (traceContextMap.has(requestId)) {
1709
1710
  return traceContextMap.get(requestId)
@@ -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
- if (meta && meta.traceId && meta.spanId) {
1848
- traceContext = {
1849
- traceId: meta.traceId,
1850
- spanId: meta.spanId,
1851
- parentSpanId: meta.parentSpanId || null
1862
+ if (meta && meta.traceId) {
1863
+ const tid = toTraceIdHex32(meta.traceId)
1864
+ if (tid) {
1865
+ traceContext = {
1866
+ traceId: tid,
1867
+ spanId: meta.spanId != null && meta.spanId !== '' ? String(meta.spanId).slice(0, 16) : generateSpanId(),
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'
@@ -2008,7 +2054,57 @@ async function handleLog(req, res) {
2008
2054
 
2009
2055
  res.json({ success: true, message: 'Log enviado com sucesso', index: indexName })
2010
2056
 
2011
- _enqueueOpenSearchWrite(osUrl, indexName, logEntry, serviceName)
2057
+ const docToSend = _trimLogEntryForOpenSearch(logEntry)
2058
+ _enqueueOpenSearchWrite(osUrl, indexName, docToSend, serviceName)
2059
+ }
2060
+
2061
+ const _opensearchMaxFields = Math.min(Number(process.env.AZIFY_LOGGER_OPENSEARCH_MAX_FIELDS) || 0, 2000) || 900
2062
+
2063
+ function _countFields(obj, depth = 0) {
2064
+ if (depth > 15) return 0
2065
+ if (obj == null || typeof obj !== 'object') return 1
2066
+ let n = 0
2067
+ for (const key of Object.keys(obj)) {
2068
+ const v = obj[key]
2069
+ if (v != null && typeof v === 'object' && !Array.isArray(v) && !Buffer.isBuffer(v)) n += _countFields(v, depth + 1)
2070
+ else n += 1
2071
+ }
2072
+ return n
2073
+ }
2074
+
2075
+ function _stringifyValue(value) {
2076
+ try {
2077
+ return typeof value === 'object' ? JSON.stringify(value) : String(value)
2078
+ } catch (_) {
2079
+ return '[Unable to serialize]'
2080
+ }
2081
+ }
2082
+
2083
+ function _trimLogEntryForOpenSearch(doc) {
2084
+ if (_countFields(doc) <= _opensearchMaxFields) return doc
2085
+ const out = {}
2086
+ for (const key of Object.keys(doc)) {
2087
+ const value = doc[key]
2088
+ if (value != null && typeof value === 'object' && !Array.isArray(value) && !Buffer.isBuffer(value)) {
2089
+ if (_countFields(value) > 80) {
2090
+ out[key + 'Json'] = _stringifyValue(value)
2091
+ } else {
2092
+ out[key] = value
2093
+ }
2094
+ } else {
2095
+ out[key] = value
2096
+ }
2097
+ }
2098
+ if (_countFields(out) > _opensearchMaxFields) {
2099
+ for (const k of Object.keys(out)) {
2100
+ const v = out[k]
2101
+ if (v != null && typeof v === 'object' && !Array.isArray(v) && !Buffer.isBuffer(v)) {
2102
+ out[k + 'Json'] = _stringifyValue(v)
2103
+ delete out[k]
2104
+ }
2105
+ }
2106
+ }
2107
+ return out
2012
2108
  }
2013
2109
 
2014
2110
  const _openSearchWriteQueue = []
@@ -2026,14 +2122,82 @@ const _logDedupeWindowMs = 5000
2026
2122
  const _logDedupeMaxKeys = 15000
2027
2123
  const _logDedupeSeen = new Map()
2028
2124
  const _logDedupeBucketMs = 80
2125
+ const _logDedupeGenericBucketMs = 250
2126
+
2127
+ function _normalizeReqResMessageForDedupe(msg) {
2128
+ const s = typeof msg === 'string' ? msg : String(msg || '')
2129
+ return s.replace(/\s+\d+(\.\d+)?ms\s*$/i, '').trimEnd()
2130
+ }
2131
+
2132
+ function _httpClientDedupeFingerprint(logEntry) {
2133
+ if (!logEntry || logEntry.source !== 'http-client') return null
2134
+ const msg = typeof logEntry.message === 'string' ? logEntry.message.substring(0, 500) : String(logEntry.message || '')
2135
+ const msgLower = msg.trimStart().toLowerCase()
2136
+ const isReq = msgLower.startsWith('[request]')
2137
+ const isRes = msgLower.startsWith('[response]')
2138
+ if (!isReq && !isRes) return null
2139
+
2140
+ const methodRaw =
2141
+ (logEntry.method != null && String(logEntry.method).trim()) ||
2142
+ (logEntry.request && logEntry.request.method != null && String(logEntry.request.method).trim()) ||
2143
+ ''
2144
+ const m = methodRaw.toUpperCase()
2145
+ let url = ''
2146
+ if (logEntry.url != null && String(logEntry.url).trim()) {
2147
+ url = String(logEntry.url).trim()
2148
+ } else if (logEntry.request && typeof logEntry.request === 'object') {
2149
+ url = String(logEntry.request.url || logEntry.request.path || '').trim()
2150
+ }
2151
+ if (!m || !url) return null
2152
+
2153
+ if (isReq) {
2154
+ return `REQUEST|${m}|${url}`
2155
+ }
2156
+ let st = logEntry.statusCode != null ? String(logEntry.statusCode).trim() : ''
2157
+ if (!st && /\bERROR\b/i.test(msg)) {
2158
+ st = 'ERR'
2159
+ }
2160
+ if (!st) {
2161
+ const mStatus = msg.match(/\s(\d{3})\s+(?:\d+(?:\.\d+)?ms\s*)?$/i)
2162
+ if (mStatus) st = mStatus[1]
2163
+ }
2164
+ return `RESPONSE|${m}|${url}|${st || 'na'}`
2165
+ }
2029
2166
 
2030
2167
  function _logDedupeKey(serviceName, logEntry) {
2031
2168
  const msg = typeof logEntry.message === 'string' ? logEntry.message.substring(0, 500) : String(logEntry.message || '')
2169
+ const msgLower = msg.trimStart().toLowerCase()
2170
+ const isReqRes = msgLower.includes('[request]') || msgLower.includes('[response]')
2032
2171
  const traceId = logEntry.traceId != null ? String(logEntry.traceId).trim() : ''
2033
2172
  const spanId = logEntry.spanId != null ? String(logEntry.spanId).trim() : ''
2173
+ const msgNorm = _normalizeReqResMessageForDedupe(msg)
2174
+ const msgSig = msgNorm.length > 200 ? msgNorm.slice(0, 200) : msgNorm
2175
+ if (isReqRes) {
2176
+ const kind = msgLower.startsWith('[request]')
2177
+ ? 'REQUEST'
2178
+ : msgLower.startsWith('[response]')
2179
+ ? 'RESPONSE'
2180
+ : (msgLower.includes('[request]') ? 'REQUEST' : 'RESPONSE')
2181
+ const httpFp = _httpClientDedupeFingerprint(logEntry)
2182
+ if (httpFp) {
2183
+ if (traceId) return `rh|${serviceName}|${traceId}|${httpFp}`
2184
+ const requestId = (logEntry.requestId != null ? String(logEntry.requestId).trim() : '') || ''
2185
+ if (requestId) return `rh|${serviceName}|${requestId}|${httpFp}`
2186
+ return null
2187
+ }
2188
+ if (traceId && spanId) return `r|${serviceName}|${traceId}|${spanId}|${kind}|${msgSig}`
2189
+ const ts = logEntry['@timestamp'] != null ? Number(logEntry['@timestamp']) : Date.now()
2190
+ const bucket = Math.floor(ts / _logDedupeBucketMs) * _logDedupeBucketMs
2191
+ return `r|${serviceName}|${bucket}|${kind}|${msgSig}`
2192
+ }
2034
2193
  if (traceId && spanId) return `t|${serviceName}|${traceId}|${spanId}|${msg}`
2035
2194
  const requestId = (logEntry.requestId != null ? String(logEntry.requestId).trim() : '') || ''
2036
- if (!requestId) return null
2195
+ if (!requestId) {
2196
+ const ts = logEntry['@timestamp'] != null ? Number(logEntry['@timestamp']) : Date.now()
2197
+ const bucket = Math.floor(ts / _logDedupeGenericBucketMs) * _logDedupeGenericBucketMs
2198
+ const msgSig = msg.length > 200 ? msg.slice(0, 200) : msg
2199
+ return `g|${serviceName}|${bucket}|${msgSig}`
2200
+ }
2037
2201
  const ts = logEntry['@timestamp'] != null ? Number(logEntry['@timestamp']) : Date.now()
2038
2202
  const bucket = Math.floor(ts / _logDedupeBucketMs) * _logDedupeBucketMs
2039
2203
  return `b|${serviceName}|${requestId}|${msg}|${bucket}`
@@ -2059,6 +2223,12 @@ function _enqueueOpenSearchWrite(osUrl, indexName, logEntry, serviceName) {
2059
2223
  if (expiry != null && expiry > now) return
2060
2224
  _logDedupeSeen.set(key, now + _logDedupeWindowMs)
2061
2225
  }
2226
+ if (process.env.AZIFY_LOGGER_DEBUG === '1') {
2227
+ const msg = typeof logEntry.message === 'string' ? logEntry.message : ''
2228
+ if (msg.toLowerCase().includes('[request]') || msg.toLowerCase().includes('[response]')) {
2229
+ console.log('[azify-logger][server] REQUEST/RESPONSE enqueued for OpenSearch:', msg.slice(0, 70))
2230
+ }
2231
+ }
2062
2232
  _openSearchWriteQueue.push({ osUrl, indexName, logEntry, serviceName })
2063
2233
  _drainOpenSearchQueue()
2064
2234
  }
@@ -2081,11 +2251,17 @@ function _drainOpenSearchQueue() {
2081
2251
  })
2082
2252
  .then(() => {
2083
2253
  _openSearchFailuresInRow = 0
2254
+ if (process.env.AZIFY_LOGGER_DEBUG === '1') {
2255
+ const msg = typeof job.logEntry.message === 'string' ? job.logEntry.message : ''
2256
+ if (msg.toLowerCase().includes('[request]') || msg.toLowerCase().includes('[response]')) {
2257
+ console.log('[azify-logger][server] REQUEST/RESPONSE written to OpenSearch index=', job.indexName, 'msg=', msg.slice(0, 60))
2258
+ }
2259
+ }
2084
2260
  const SETUP_THROTTLE_MS = 10 * 60 * 1000
2085
2261
  if (!handleLog._lastSetupTime) handleLog._lastSetupTime = new Map()
2086
2262
  const last = handleLog._lastSetupTime.get(job.serviceName) || 0
2087
2263
  const now = Date.now()
2088
- const shouldRunSetup = job.serviceName !== 'unknown' && job.serviceName !== 'unknown-service' && (now - last >= SETUP_THROTTLE_MS)
2264
+ const shouldRunSetup = job.serviceName !== 'unknown' && job.serviceName !== 'unknown-service' && job.serviceName !== 'unknown-app' && (now - last >= SETUP_THROTTLE_MS)
2089
2265
  if (shouldRunSetup) {
2090
2266
  handleLog._lastSetupTime.set(job.serviceName, now)
2091
2267
  setupGrafanaForApp(job.serviceName).then(() => {
package/store.d.ts ADDED
@@ -0,0 +1,68 @@
1
+ export type AzifyRequestContext = {
2
+ traceId: string
3
+ spanId: string
4
+ parentSpanId: string | null
5
+ requestId: string | null
6
+ }
7
+
8
+ export type AzifyJobTraceRecord = {
9
+ traceId: string
10
+ spanId?: string | null
11
+ parentSpanId?: string | null
12
+ requestId?: string | null
13
+ }
14
+
15
+ export declare const AZIFY_JOB_TRACE_FIELD: '__azifyTraceContext'
16
+
17
+ export function prepareBullJobTraceContext(data: unknown): {
18
+ cleanedForLog: unknown
19
+ ctx: AzifyRequestContext
20
+ dataForDownstream: unknown
21
+ }
22
+
23
+ export function captureOriginTraceMeta(): {
24
+ originTraceId?: string
25
+ originRequestId?: string
26
+ }
27
+
28
+ export function addCurrentTraceToBullJobPayload<T>(data: T): T
29
+
30
+ export function traceAttachmentForBullJob(): Record<string, unknown>
31
+
32
+ export function runBullJobHandlerInTraceContext<TJob extends { data: unknown }, R>(
33
+ job: TJob,
34
+ handler: (job: TJob) => R,
35
+ ): R
36
+
37
+ export function runInBullJobTraceContext<T>(
38
+ jobData: unknown,
39
+ fn: (x: {
40
+ ctx: AzifyRequestContext
41
+ cleanedForLog: unknown
42
+ dataForDownstream: unknown
43
+ }) => T,
44
+ ): T
45
+
46
+ export function runNestBullJobWithTrace<TJob extends { data: unknown }, R>(
47
+ job: TJob,
48
+ fn: (x: {
49
+ job: TJob
50
+ ctx: AzifyRequestContext
51
+ cleanedForLog: unknown
52
+ dataForDownstream: unknown
53
+ }) => R,
54
+ ): R
55
+
56
+ export function startRequestContext(
57
+ initial?: Record<string, unknown>,
58
+ ): AzifyRequestContext
59
+ export function runWithRequestContext<T>(ctx: AzifyRequestContext, fn: () => T): T
60
+ export function getRequestContext(): AzifyRequestContext | null
61
+ export function getActiveRequestId(): string | null
62
+ export function getLastJobContext(): AzifyRequestContext | null
63
+ export function setLastJobContext(ctx: AzifyRequestContext | null): void
64
+ export function mergeTraceIntoJobPayload(
65
+ data: unknown,
66
+ traceContext: AzifyJobTraceRecord | null,
67
+ ): unknown
68
+ export function toTraceIdHex(hex32: string | null | undefined): string
package/store.js CHANGED
@@ -1,6 +1,17 @@
1
1
  const { AsyncLocalStorage } = require('async_hooks')
2
2
 
3
+ const AZIFY_JOB_TRACE_FIELD = '__azifyTraceContext'
4
+
3
5
  const als = new AsyncLocalStorage()
6
+ let lastJobContext = null
7
+
8
+ function setLastJobContext(ctx) {
9
+ lastJobContext = ctx || null
10
+ }
11
+
12
+ function getLastJobContext() {
13
+ return lastJobContext
14
+ }
4
15
 
5
16
  function fastGenerateId(length = 16) {
6
17
  const chars = '0123456789abcdef'
@@ -12,21 +23,32 @@ function fastGenerateId(length = 16) {
12
23
  return result
13
24
  }
14
25
 
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)}`
26
+ function newRequestIdForJob() {
27
+ try {
28
+ const { randomUUID } = require('crypto')
29
+ if (typeof randomUUID === 'function') {
30
+ return randomUUID()
31
+ }
32
+ } catch (_) {}
33
+ return `${fastGenerateId(16)}-${fastGenerateId(8)}`
18
34
  }
19
35
 
20
- function startRequestContext(initial = {}) {
36
+ function toTraceIdHex(hex32) {
37
+ const raw = hex32 != null && typeof hex32 === 'string' ? String(hex32).replace(/[^0-9a-fA-F]/g, '').slice(0, 32) : ''
38
+ const hex = raw || fastGenerateId(16)
39
+ return hex.padStart(32, '0').slice(0, 32)
40
+ }
41
+
42
+ function startRequestContext(initial) {
43
+ if (!initial || typeof initial !== 'object') initial = {}
21
44
  const traceHex = initial.traceHex || fastGenerateId(16)
22
45
  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
46
+ return {
47
+ traceId: toTraceIdHex(traceHex),
48
+ spanId: spanHex != null && spanHex !== '' ? String(spanHex).slice(0, 16) : fastGenerateId(8),
49
+ parentSpanId: initial.parentSpanId != null && initial.parentSpanId !== '' ? String(initial.parentSpanId) : null,
50
+ requestId: initial.requestId != null && initial.requestId !== '' ? String(initial.requestId) : null
28
51
  }
29
- return ctx
30
52
  }
31
53
 
32
54
  function runWithRequestContext(ctx, fn) {
@@ -37,11 +59,168 @@ function getRequestContext() {
37
59
  return als.getStore() || null
38
60
  }
39
61
 
62
+ function getActiveRequestId() {
63
+ const ctx = getRequestContext()
64
+ if (!ctx?.requestId || String(ctx.requestId) === '') return null
65
+ return String(ctx.requestId)
66
+ }
67
+
68
+ function captureOriginTraceMeta() {
69
+ try {
70
+ const ctx = getRequestContext() || getLastJobContext()
71
+ if (!ctx || typeof ctx !== 'object') return {}
72
+ const out = {}
73
+ if (ctx.traceId != null && String(ctx.traceId) !== '') {
74
+ out.originTraceId = String(ctx.traceId)
75
+ }
76
+ if (ctx.requestId != null && String(ctx.requestId) !== '') {
77
+ out.originRequestId = String(ctx.requestId)
78
+ }
79
+ return out
80
+ } catch (_) {
81
+ return {}
82
+ }
83
+ }
84
+
85
+ function mergeTraceIntoJobPayload(data, traceContext) {
86
+ if (!traceContext || !traceContext.traceId) return data
87
+ const base = data && typeof data === 'object' && !Array.isArray(data) ? data : {}
88
+ return {
89
+ ...base,
90
+ traceId: traceContext.traceId,
91
+ spanId: traceContext.spanId ?? null,
92
+ parentSpanId: traceContext.parentSpanId ?? null,
93
+ requestId: traceContext.requestId ?? null
94
+ }
95
+ }
96
+
97
+ function normalizedJobTrace(raw) {
98
+ if (!raw || typeof raw !== 'object' || typeof raw.traceId !== 'string') return null
99
+ return {
100
+ traceId: String(raw.traceId),
101
+ spanId: raw.spanId != null ? raw.spanId : null,
102
+ parentSpanId: raw.parentSpanId != null ? raw.parentSpanId : null,
103
+ requestId: raw.requestId != null && raw.requestId !== '' ? String(raw.requestId) : null
104
+ }
105
+ }
106
+
107
+ function alsContextFromJobTrace(trace) {
108
+ const fallbackRid = newRequestIdForJob()
109
+ if (!trace) {
110
+ return startRequestContext({ requestId: fallbackRid })
111
+ }
112
+ const traceHex = String(trace.traceId).replace(/[^0-9a-fA-F]/g, '').slice(0, 32)
113
+ const spanHex = trace.spanId
114
+ ? String(trace.spanId).replace(/[^0-9a-fA-F]/g, '').slice(0, 16)
115
+ : undefined
116
+ return startRequestContext({
117
+ traceHex: traceHex || undefined,
118
+ spanHex: spanHex || undefined,
119
+ parentSpanId: trace.parentSpanId != null && trace.parentSpanId !== '' ? String(trace.parentSpanId) : undefined,
120
+ requestId: trace.requestId || fallbackRid
121
+ })
122
+ }
123
+
124
+ function captureTraceForJobEnqueue() {
125
+ try {
126
+ const ctx = getRequestContext() || getLastJobContext()
127
+ if (!ctx || typeof ctx !== 'object' || ctx.traceId == null || String(ctx.traceId) === '') {
128
+ return null
129
+ }
130
+ return normalizedJobTrace({
131
+ traceId: String(ctx.traceId),
132
+ spanId: ctx.spanId,
133
+ parentSpanId: ctx.parentSpanId,
134
+ requestId: ctx.requestId
135
+ })
136
+ } catch (_) {
137
+ return null
138
+ }
139
+ }
140
+
141
+ function attachTraceToBullJobData(data, traceRecord) {
142
+ if (!traceRecord || !traceRecord.traceId) return data
143
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return data
144
+ return {
145
+ ...data,
146
+ [AZIFY_JOB_TRACE_FIELD]: traceRecord
147
+ }
148
+ }
149
+
150
+ function addCurrentTraceToBullJobPayload(data) {
151
+ const trace = captureTraceForJobEnqueue()
152
+ if (!trace) return data
153
+ return attachTraceToBullJobData(data, trace)
154
+ }
155
+
156
+ function traceAttachmentForBullJob() {
157
+ const trace = captureTraceForJobEnqueue()
158
+ if (!trace) return {}
159
+ return { [AZIFY_JOB_TRACE_FIELD]: trace }
160
+ }
161
+
162
+ function prepareBullJobTraceContext(data) {
163
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
164
+ const ctx = alsContextFromJobTrace(null)
165
+ return { cleanedForLog: data, ctx, dataForDownstream: data }
166
+ }
167
+ const rawTrace = data[AZIFY_JOB_TRACE_FIELD]
168
+ const hasKey = Object.prototype.hasOwnProperty.call(data, AZIFY_JOB_TRACE_FIELD)
169
+ let cleaned
170
+ if (hasKey) {
171
+ const copy = { ...data }
172
+ delete copy[AZIFY_JOB_TRACE_FIELD]
173
+ cleaned = copy
174
+ } else {
175
+ cleaned = { ...data }
176
+ }
177
+ const trace = normalizedJobTrace(rawTrace)
178
+ const ctx = alsContextFromJobTrace(trace)
179
+ const dataForDownstream = trace
180
+ ? { ...cleaned, [AZIFY_JOB_TRACE_FIELD]: trace }
181
+ : cleaned
182
+ return { cleanedForLog: cleaned, ctx, dataForDownstream }
183
+ }
184
+
185
+ function runBullJobHandlerInTraceContext(job, handler) {
186
+ const { ctx, cleanedForLog } = prepareBullJobTraceContext(job.data)
187
+ const nextJob =
188
+ cleanedForLog !== job.data ? { ...job, data: cleanedForLog } : job
189
+ return runWithRequestContext(ctx, () => handler(nextJob))
190
+ }
191
+
192
+ function runInBullJobTraceContext(jobData, fn) {
193
+ const { ctx, cleanedForLog, dataForDownstream } =
194
+ prepareBullJobTraceContext(jobData)
195
+ return runWithRequestContext(ctx, () =>
196
+ fn({ ctx, cleanedForLog, dataForDownstream }),
197
+ )
198
+ }
199
+
200
+ function runNestBullJobWithTrace(job, fn) {
201
+ return runInBullJobTraceContext(job.data, (bundle) =>
202
+ fn({ job, ...bundle }),
203
+ )
204
+ }
205
+
40
206
  module.exports = {
41
207
  als,
208
+ AZIFY_JOB_TRACE_FIELD,
42
209
  startRequestContext,
43
210
  runWithRequestContext,
44
- getRequestContext
211
+ getRequestContext,
212
+ getActiveRequestId,
213
+ getLastJobContext,
214
+ setLastJobContext,
215
+ mergeTraceIntoJobPayload,
216
+ prepareBullJobTraceContext,
217
+ runBullJobHandlerInTraceContext,
218
+ runInBullJobTraceContext,
219
+ runNestBullJobWithTrace,
220
+ addCurrentTraceToBullJobPayload,
221
+ traceAttachmentForBullJob,
222
+ captureOriginTraceMeta,
223
+ toTraceIdHex
45
224
  }
46
225
 
47
226