azify-logger 1.0.45 → 1.0.47
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 +4 -2
- package/register.js +33 -20
- package/server.js +120 -11
- package/store.d.ts +68 -0
- package/store.js +153 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azify-logger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.47",
|
|
4
4
|
"description": "Azify Logger Client - Centralized logging for OpenSearch",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"uuid": "^9.0.1"
|
|
69
69
|
},
|
|
70
70
|
"overrides": {
|
|
71
|
+
"semver": "^7.5.2",
|
|
71
72
|
"@opentelemetry/api": "1.0.4",
|
|
72
73
|
"@opentelemetry/core": "1.0.1",
|
|
73
74
|
"@opentelemetry/resources": "1.0.1",
|
|
@@ -76,7 +77,7 @@
|
|
|
76
77
|
"@opentelemetry/auto-instrumentations-node": "0.27.0"
|
|
77
78
|
},
|
|
78
79
|
"engines": {
|
|
79
|
-
"node": ">=
|
|
80
|
+
"node": ">=20 <=22"
|
|
80
81
|
},
|
|
81
82
|
"bin": {
|
|
82
83
|
"azify-logger-worker": "scripts/redis-worker.js"
|
|
@@ -91,6 +92,7 @@
|
|
|
91
92
|
"init.js",
|
|
92
93
|
"register.js",
|
|
93
94
|
"store.js",
|
|
95
|
+
"store.d.ts",
|
|
94
96
|
"register-otel.js",
|
|
95
97
|
"register-restify.js",
|
|
96
98
|
"middleware-restify.js",
|
package/register.js
CHANGED
|
@@ -1137,21 +1137,24 @@ try {
|
|
|
1137
1137
|
const traceCtx = ctx || otelCtx
|
|
1138
1138
|
const method = (options?.method || 'GET').toUpperCase()
|
|
1139
1139
|
const urlString = typeof url === 'string' ? url : (url && url.toString && url.toString()) || 'unknown'
|
|
1140
|
-
const
|
|
1140
|
+
const existingHeaders = (options && options.headers && typeof options.headers === 'object') ? options.headers : {}
|
|
1141
|
+
const headerTraceId = existingHeaders['x-trace-id'] || existingHeaders['X-Trace-ID'] || null
|
|
1142
|
+
const headerSpanId = existingHeaders['x-span-id'] || existingHeaders['X-Span-ID'] || null
|
|
1143
|
+
const headerParentSpanId = existingHeaders['x-parent-span-id'] || existingHeaders['X-Parent-Span-ID'] || null
|
|
1144
|
+
const headerRequestId = existingHeaders['x-request-id'] || existingHeaders['X-Request-ID'] || null
|
|
1145
|
+
const rawTraceId = headerTraceId || traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1141
1146
|
const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
|
|
1142
|
-
const parentSpanId = traceCtx?.spanId || null
|
|
1143
|
-
const requestId = traceCtx?.requestId || ctx?.requestId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1144
|
-
const spanId = randomBytes(8).toString('hex')
|
|
1147
|
+
const parentSpanId = headerParentSpanId || traceCtx?.spanId || null
|
|
1148
|
+
const requestId = headerRequestId || traceCtx?.requestId || ctx?.requestId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1149
|
+
const spanId = headerSpanId || randomBytes(8).toString('hex')
|
|
1145
1150
|
const requestMeta = { traceId, spanId, parentSpanId, requestId, method, url: urlString }
|
|
1146
1151
|
markSource(requestMeta, 'http-client')
|
|
1147
1152
|
const startTime = performance.now()
|
|
1148
1153
|
if (HTTP_CLIENT_MODE !== 'off') {
|
|
1149
1154
|
sendOutboundLog('info', `[REQUEST] ${method} ${urlString}`, requestMeta)
|
|
1150
1155
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
options = { ...options, headers: { ...headers, 'X-Trace-ID': traceId, 'X-Span-ID': spanId, 'X-Parent-Span-ID': traceCtx.parentSpanId || '', 'X-Request-ID': requestId } }
|
|
1154
|
-
}
|
|
1156
|
+
const headers = (options && options.headers && typeof options.headers === 'object') ? options.headers : {}
|
|
1157
|
+
options = { ...options, headers: { ...headers, 'X-Trace-ID': traceId, 'X-Span-ID': spanId, 'X-Parent-Span-ID': parentSpanId || '', 'X-Request-ID': requestId } }
|
|
1155
1158
|
const wrappedCb = callback ? function(err, data) {
|
|
1156
1159
|
const duration = Number((performance.now() - startTime).toFixed(2))
|
|
1157
1160
|
if (HTTP_CLIENT_MODE !== 'off') {
|
|
@@ -1203,19 +1206,24 @@ try {
|
|
|
1203
1206
|
const origin = (opts && opts.origin) || ''
|
|
1204
1207
|
const path = (opts && opts.path) != null ? opts.path : '/'
|
|
1205
1208
|
const urlString = origin ? (origin + (path.startsWith('/') ? path : '/' + path)) : 'unknown'
|
|
1206
|
-
const
|
|
1209
|
+
const existingHeaders = (opts && opts.headers && typeof opts.headers === 'object') ? opts.headers : {}
|
|
1210
|
+
const headerTraceId = existingHeaders['x-trace-id'] || existingHeaders['X-Trace-ID'] || null
|
|
1211
|
+
const headerSpanId = existingHeaders['x-span-id'] || existingHeaders['X-Span-ID'] || null
|
|
1212
|
+
const headerParentSpanId = existingHeaders['x-parent-span-id'] || existingHeaders['X-Parent-Span-ID'] || null
|
|
1213
|
+
const headerRequestId = existingHeaders['x-request-id'] || existingHeaders['X-Request-ID'] || null
|
|
1214
|
+
const rawTraceId = headerTraceId || traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1207
1215
|
const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
|
|
1208
|
-
const parentSpanId = traceCtx?.spanId || null
|
|
1209
|
-
const requestId = traceCtx?.requestId || ctx?.requestId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1210
|
-
const spanId = randomBytes(8).toString('hex')
|
|
1216
|
+
const parentSpanId = headerParentSpanId || traceCtx?.spanId || null
|
|
1217
|
+
const requestId = headerRequestId || traceCtx?.requestId || ctx?.requestId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1218
|
+
const spanId = headerSpanId || randomBytes(8).toString('hex')
|
|
1211
1219
|
const requestMeta = { traceId, spanId, parentSpanId, requestId, method: methodStr, url: urlString }
|
|
1212
1220
|
markSource(requestMeta, 'http-client')
|
|
1213
1221
|
const startTime = performance.now()
|
|
1214
1222
|
if (HTTP_CLIENT_MODE !== 'off') {
|
|
1215
1223
|
sendOutboundLog('info', `[REQUEST] ${methodStr} ${urlString}`, requestMeta)
|
|
1216
1224
|
}
|
|
1217
|
-
if (
|
|
1218
|
-
const headers = { ...(opts.headers || {}), 'X-Trace-ID': traceId, 'X-Span-ID': spanId, 'X-Parent-Span-ID':
|
|
1225
|
+
if (opts && typeof opts === 'object') {
|
|
1226
|
+
const headers = { ...(opts.headers || {}), 'X-Trace-ID': traceId, 'X-Span-ID': spanId, 'X-Parent-Span-ID': parentSpanId || '', 'X-Request-ID': requestId }
|
|
1219
1227
|
opts = { ...opts, headers }
|
|
1220
1228
|
}
|
|
1221
1229
|
const wrappedCb = typeof callback === 'function' ? function(err, data) {
|
|
@@ -1273,19 +1281,24 @@ try {
|
|
|
1273
1281
|
const origin = (opts && opts.origin) || ''
|
|
1274
1282
|
const path = (opts && opts.path) != null ? opts.path : '/'
|
|
1275
1283
|
const urlString = origin ? (origin + (path.startsWith('/') ? path : '/' + path)) : 'unknown'
|
|
1276
|
-
const
|
|
1284
|
+
const existingHeaders = (opts && opts.headers && typeof opts.headers === 'object') ? opts.headers : {}
|
|
1285
|
+
const headerTraceId = existingHeaders['x-trace-id'] || existingHeaders['X-Trace-ID'] || null
|
|
1286
|
+
const headerSpanId = existingHeaders['x-span-id'] || existingHeaders['X-Span-ID'] || null
|
|
1287
|
+
const headerParentSpanId = existingHeaders['x-parent-span-id'] || existingHeaders['X-Parent-Span-ID'] || null
|
|
1288
|
+
const headerRequestId = existingHeaders['x-request-id'] || existingHeaders['X-Request-ID'] || null
|
|
1289
|
+
const rawTraceId = headerTraceId || traceCtx?.traceId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1277
1290
|
const traceId = typeof rawTraceId === 'string' ? toTraceIdHex(rawTraceId) : rawTraceId
|
|
1278
|
-
const parentSpanId = traceCtx?.spanId || null
|
|
1279
|
-
const requestId = traceCtx?.requestId || ctx?.requestId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1280
|
-
const spanId = randomBytes(8).toString('hex')
|
|
1291
|
+
const parentSpanId = headerParentSpanId || traceCtx?.spanId || null
|
|
1292
|
+
const requestId = headerRequestId || traceCtx?.requestId || ctx?.requestId || (require('crypto').randomUUID && require('crypto').randomUUID())
|
|
1293
|
+
const spanId = headerSpanId || randomBytes(8).toString('hex')
|
|
1281
1294
|
const requestMeta = { traceId, spanId, parentSpanId, requestId, method: methodStr, url: urlString }
|
|
1282
1295
|
markSource(requestMeta, 'http-client')
|
|
1283
1296
|
const startTime = performance.now()
|
|
1284
1297
|
if (HTTP_CLIENT_MODE !== 'off') {
|
|
1285
1298
|
sendOutboundLog('info', `[REQUEST] ${methodStr} ${urlString}`, requestMeta)
|
|
1286
1299
|
}
|
|
1287
|
-
if (
|
|
1288
|
-
const headers = { ...(opts.headers || {}), 'X-Trace-ID': traceId, 'X-Span-ID': spanId, 'X-Parent-Span-ID':
|
|
1300
|
+
if (opts && typeof opts === 'object') {
|
|
1301
|
+
const headers = { ...(opts.headers || {}), 'X-Trace-ID': traceId, 'X-Span-ID': spanId, 'X-Parent-Span-ID': parentSpanId || '', 'X-Request-ID': requestId }
|
|
1289
1302
|
opts = { ...opts, headers }
|
|
1290
1303
|
}
|
|
1291
1304
|
const wrappedCb = typeof callback === 'function' ? function(err, data) {
|
package/server.js
CHANGED
|
@@ -1697,14 +1697,14 @@ async function registerAlertRulesForApp(appName, rules, options = {}) {
|
|
|
1697
1697
|
}
|
|
1698
1698
|
|
|
1699
1699
|
function generateTraceId() {
|
|
1700
|
-
return
|
|
1701
|
-
Math.random().toString(36).substring(2, 15)
|
|
1700
|
+
return require('crypto').randomBytes(16).toString('hex')
|
|
1702
1701
|
}
|
|
1703
1702
|
|
|
1704
1703
|
function generateSpanId() {
|
|
1705
|
-
return
|
|
1704
|
+
return require('crypto').randomBytes(8).toString('hex')
|
|
1706
1705
|
}
|
|
1707
1706
|
|
|
1707
|
+
|
|
1708
1708
|
function getOrCreateTraceContext(requestId) {
|
|
1709
1709
|
if (traceContextMap.has(requestId)) {
|
|
1710
1710
|
return traceContextMap.get(requestId)
|
|
@@ -1859,12 +1859,12 @@ async function handleLog(req, res) {
|
|
|
1859
1859
|
|
|
1860
1860
|
let traceContext = null
|
|
1861
1861
|
|
|
1862
|
-
if (meta && meta.traceId
|
|
1862
|
+
if (meta && meta.traceId) {
|
|
1863
1863
|
const tid = toTraceIdHex32(meta.traceId)
|
|
1864
1864
|
if (tid) {
|
|
1865
1865
|
traceContext = {
|
|
1866
1866
|
traceId: tid,
|
|
1867
|
-
spanId: String(meta.spanId).slice(0, 16),
|
|
1867
|
+
spanId: meta.spanId != null && meta.spanId !== '' ? String(meta.spanId).slice(0, 16) : generateSpanId(),
|
|
1868
1868
|
parentSpanId: meta.parentSpanId != null && meta.parentSpanId !== '' ? String(meta.parentSpanId) : null
|
|
1869
1869
|
}
|
|
1870
1870
|
}
|
|
@@ -2054,7 +2054,57 @@ async function handleLog(req, res) {
|
|
|
2054
2054
|
|
|
2055
2055
|
res.json({ success: true, message: 'Log enviado com sucesso', index: indexName })
|
|
2056
2056
|
|
|
2057
|
-
|
|
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
|
|
2058
2108
|
}
|
|
2059
2109
|
|
|
2060
2110
|
const _openSearchWriteQueue = []
|
|
@@ -2072,23 +2122,82 @@ const _logDedupeWindowMs = 5000
|
|
|
2072
2122
|
const _logDedupeMaxKeys = 15000
|
|
2073
2123
|
const _logDedupeSeen = new Map()
|
|
2074
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
|
+
}
|
|
2075
2166
|
|
|
2076
2167
|
function _logDedupeKey(serviceName, logEntry) {
|
|
2077
2168
|
const msg = typeof logEntry.message === 'string' ? logEntry.message.substring(0, 500) : String(logEntry.message || '')
|
|
2078
|
-
const msgLower = msg.toLowerCase()
|
|
2169
|
+
const msgLower = msg.trimStart().toLowerCase()
|
|
2079
2170
|
const isReqRes = msgLower.includes('[request]') || msgLower.includes('[response]')
|
|
2080
2171
|
const traceId = logEntry.traceId != null ? String(logEntry.traceId).trim() : ''
|
|
2081
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
|
|
2082
2175
|
if (isReqRes) {
|
|
2083
|
-
const kind = msgLower.
|
|
2084
|
-
|
|
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}`
|
|
2085
2189
|
const ts = logEntry['@timestamp'] != null ? Number(logEntry['@timestamp']) : Date.now()
|
|
2086
2190
|
const bucket = Math.floor(ts / _logDedupeBucketMs) * _logDedupeBucketMs
|
|
2087
|
-
return `r|${serviceName}|${bucket}|${kind}`
|
|
2191
|
+
return `r|${serviceName}|${bucket}|${kind}|${msgSig}`
|
|
2088
2192
|
}
|
|
2089
2193
|
if (traceId && spanId) return `t|${serviceName}|${traceId}|${spanId}|${msg}`
|
|
2090
2194
|
const requestId = (logEntry.requestId != null ? String(logEntry.requestId).trim() : '') || ''
|
|
2091
|
-
if (!requestId)
|
|
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
|
+
}
|
|
2092
2201
|
const ts = logEntry['@timestamp'] != null ? Number(logEntry['@timestamp']) : Date.now()
|
|
2093
2202
|
const bucket = Math.floor(ts / _logDedupeBucketMs) * _logDedupeBucketMs
|
|
2094
2203
|
return `b|${serviceName}|${requestId}|${msg}|${bucket}`
|
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,5 +1,7 @@
|
|
|
1
1
|
const { AsyncLocalStorage } = require('async_hooks')
|
|
2
2
|
|
|
3
|
+
const AZIFY_JOB_TRACE_FIELD = '__azifyTraceContext'
|
|
4
|
+
|
|
3
5
|
const als = new AsyncLocalStorage()
|
|
4
6
|
let lastJobContext = null
|
|
5
7
|
|
|
@@ -21,6 +23,16 @@ function fastGenerateId(length = 16) {
|
|
|
21
23
|
return result
|
|
22
24
|
}
|
|
23
25
|
|
|
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)}`
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
function toTraceIdHex(hex32) {
|
|
25
37
|
const raw = hex32 != null && typeof hex32 === 'string' ? String(hex32).replace(/[^0-9a-fA-F]/g, '').slice(0, 32) : ''
|
|
26
38
|
const hex = raw || fastGenerateId(16)
|
|
@@ -47,6 +59,29 @@ function getRequestContext() {
|
|
|
47
59
|
return als.getStore() || null
|
|
48
60
|
}
|
|
49
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
|
+
|
|
50
85
|
function mergeTraceIntoJobPayload(data, traceContext) {
|
|
51
86
|
if (!traceContext || !traceContext.traceId) return data
|
|
52
87
|
const base = data && typeof data === 'object' && !Array.isArray(data) ? data : {}
|
|
@@ -59,14 +94,132 @@ function mergeTraceIntoJobPayload(data, traceContext) {
|
|
|
59
94
|
}
|
|
60
95
|
}
|
|
61
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
|
+
|
|
62
206
|
module.exports = {
|
|
63
207
|
als,
|
|
208
|
+
AZIFY_JOB_TRACE_FIELD,
|
|
64
209
|
startRequestContext,
|
|
65
210
|
runWithRequestContext,
|
|
66
211
|
getRequestContext,
|
|
212
|
+
getActiveRequestId,
|
|
67
213
|
getLastJobContext,
|
|
68
214
|
setLastJobContext,
|
|
69
215
|
mergeTraceIntoJobPayload,
|
|
216
|
+
prepareBullJobTraceContext,
|
|
217
|
+
runBullJobHandlerInTraceContext,
|
|
218
|
+
runInBullJobTraceContext,
|
|
219
|
+
runNestBullJobWithTrace,
|
|
220
|
+
addCurrentTraceToBullJobPayload,
|
|
221
|
+
traceAttachmentForBullJob,
|
|
222
|
+
captureOriginTraceMeta,
|
|
70
223
|
toTraceIdHex
|
|
71
224
|
}
|
|
72
225
|
|