azify-logger 1.0.26 → 1.0.29
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/README.md +26 -5
- package/index.js +40 -17
- package/middleware-express.js +267 -366
- package/middleware-fastify.js +348 -0
- package/middleware-restify.js +147 -303
- package/package.json +31 -30
- package/queue/fileQueue.js +100 -0
- package/queue/redisQueue.js +179 -0
- package/queue/workerManager.js +111 -0
- package/register-otel.js +63 -13
- package/register.js +364 -99
- package/sampling.js +79 -0
- package/scripts/redis-worker.js +439 -0
- package/server.js +169 -74
- package/store.js +10 -4
- package/streams/bunyan.d.ts +26 -0
- package/streams/bunyan.js +39 -8
- package/streams/httpQueue.js +342 -0
- package/streams/pino.d.ts +38 -0
- package/streams/pino.js +44 -7
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
const { startRequestContext } = require('./store')
|
|
2
|
+
const { createHttpLoggerTransport } = require('./streams/httpQueue')
|
|
3
|
+
const os = require('os')
|
|
4
|
+
|
|
5
|
+
function fastUUID() {
|
|
6
|
+
const timestamp = Date.now().toString(36)
|
|
7
|
+
const randomPart = Math.random().toString(36).substring(2, 15)
|
|
8
|
+
const randomPart2 = Math.random().toString(36).substring(2, 15)
|
|
9
|
+
return `${timestamp}-${randomPart}-${randomPart2}`.substring(0, 36)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function sanitizeTraceHex(value) {
|
|
13
|
+
if (!value || typeof value !== 'string') return null
|
|
14
|
+
const hex = value.replace(/[^0-9a-fA-F]/g, '').toLowerCase()
|
|
15
|
+
if (!hex) return null
|
|
16
|
+
return hex.padEnd(32, '0').slice(0, 32)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function safeSerializeBody(payload) {
|
|
20
|
+
if (payload == null) return ''
|
|
21
|
+
if (typeof payload === 'string') return payload
|
|
22
|
+
if (Buffer.isBuffer(payload)) return payload.toString('utf8')
|
|
23
|
+
try {
|
|
24
|
+
return JSON.stringify(payload)
|
|
25
|
+
} catch (err) {
|
|
26
|
+
try {
|
|
27
|
+
return String(payload)
|
|
28
|
+
} catch {
|
|
29
|
+
return ''
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const HEADER_WHITELIST = new Set([
|
|
35
|
+
'content-type',
|
|
36
|
+
'content-length',
|
|
37
|
+
'accept',
|
|
38
|
+
'accept-encoding',
|
|
39
|
+
'user-agent',
|
|
40
|
+
'host',
|
|
41
|
+
'x-request-id',
|
|
42
|
+
'x-trace-id',
|
|
43
|
+
'x-span-id',
|
|
44
|
+
'x-parent-span-id'
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
function pickHeaders(source) {
|
|
48
|
+
if (!source || typeof source !== 'object') {
|
|
49
|
+
return {}
|
|
50
|
+
}
|
|
51
|
+
const result = {}
|
|
52
|
+
for (const key in source) {
|
|
53
|
+
const lower = key.toLowerCase()
|
|
54
|
+
if (!HEADER_WHITELIST.has(lower)) continue
|
|
55
|
+
|
|
56
|
+
if (!Object.prototype.hasOwnProperty.call(source, key)) continue
|
|
57
|
+
|
|
58
|
+
const value = source[key]
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
result[key] = value.map(String)
|
|
61
|
+
} else if (value != null) {
|
|
62
|
+
result[key] = String(value)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createFastifyLoggingPlugin(options = {}) {
|
|
69
|
+
const config = {
|
|
70
|
+
serviceName: options.serviceName || process.env.APP_NAME,
|
|
71
|
+
loggerUrl: options.loggerUrl || process.env.AZIFY_LOGGER_URL || 'http://localhost:3001/log',
|
|
72
|
+
environment: options.environment || process.env.NODE_ENV,
|
|
73
|
+
captureResponseBody: options.captureResponseBody !== false && process.env.AZIFY_LOGGER_CAPTURE_RESPONSE_BODY !== 'false',
|
|
74
|
+
captureRequestBody: options.captureRequestBody !== false && process.env.AZIFY_LOGGER_CAPTURE_REQUEST_BODY !== 'false',
|
|
75
|
+
logRequest: options.logRequest !== false && process.env.AZIFY_LOGGER_LOG_REQUEST !== 'false',
|
|
76
|
+
captureHeaders: options.captureHeaders !== undefined ? options.captureHeaders : process.env.AZIFY_LOGGER_CAPTURE_HEADERS === 'true'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const transport = createHttpLoggerTransport(config.loggerUrl, {})
|
|
80
|
+
const hostname = os.hostname()
|
|
81
|
+
const serviceObj = config.serviceName ? { name: config.serviceName, version: '1.0.0' } : null
|
|
82
|
+
|
|
83
|
+
function sendLog(level, message, meta = {}) {
|
|
84
|
+
if (!transport || typeof transport.enqueue !== 'function') return
|
|
85
|
+
|
|
86
|
+
transport.enqueue({
|
|
87
|
+
level,
|
|
88
|
+
message,
|
|
89
|
+
meta
|
|
90
|
+
}, { 'content-type': 'application/json' })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return async function azifyFastifyPlugin(fastify, opts) {
|
|
94
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
95
|
+
const startTime = Date.now()
|
|
96
|
+
const method = request.method
|
|
97
|
+
const url = request.url
|
|
98
|
+
const path = request.routerPath || url
|
|
99
|
+
|
|
100
|
+
let responseChunk = null
|
|
101
|
+
let responseChunkCaptured = false
|
|
102
|
+
let logSent = false
|
|
103
|
+
|
|
104
|
+
let requestId, traceId, spanId, parentSpanId, clientIp, query, cachedHeaders
|
|
105
|
+
let idsCreated = false
|
|
106
|
+
let headersCached = false
|
|
107
|
+
let reqCtx = null
|
|
108
|
+
|
|
109
|
+
const logWithContext = (level, message, meta) => {
|
|
110
|
+
if (!reqCtx) {
|
|
111
|
+
const traceHex = sanitizeTraceHex(request.headers['x-trace-id'])
|
|
112
|
+
reqCtx = startRequestContext({
|
|
113
|
+
requestId: requestId || request.requestId || fastUUID(),
|
|
114
|
+
traceHex,
|
|
115
|
+
parentSpanId: request.headers['x-parent-span-id'] || null
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
meta.traceId = meta.traceId || reqCtx.traceId
|
|
119
|
+
meta.spanId = meta.spanId || reqCtx.spanId
|
|
120
|
+
meta.parentSpanId = meta.parentSpanId || reqCtx.parentSpanId
|
|
121
|
+
sendLog(level, message, meta)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function ensureIds() {
|
|
125
|
+
if (idsCreated) return
|
|
126
|
+
idsCreated = true
|
|
127
|
+
|
|
128
|
+
query = request.query || null
|
|
129
|
+
|
|
130
|
+
if (!reqCtx) {
|
|
131
|
+
const traceHex = sanitizeTraceHex(request.headers['x-trace-id'])
|
|
132
|
+
reqCtx = startRequestContext({
|
|
133
|
+
requestId: request.requestId || fastUUID(),
|
|
134
|
+
traceHex,
|
|
135
|
+
parentSpanId: request.headers['x-parent-span-id'] || null
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
requestId = reqCtx.requestId || request.requestId || fastUUID()
|
|
140
|
+
traceId = reqCtx.traceId
|
|
141
|
+
spanId = reqCtx.spanId
|
|
142
|
+
parentSpanId = reqCtx.parentSpanId || request.headers['x-parent-span-id'] || null
|
|
143
|
+
|
|
144
|
+
clientIp = request.ip || request.socket?.remoteAddress || 'unknown'
|
|
145
|
+
request.requestId = requestId
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ensureHeaders() {
|
|
149
|
+
if (headersCached) return
|
|
150
|
+
headersCached = true
|
|
151
|
+
if (config.captureHeaders) {
|
|
152
|
+
cachedHeaders = pickHeaders(request.headers || {})
|
|
153
|
+
} else {
|
|
154
|
+
cachedHeaders = {}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function emitResponseLog(meta, chunk) {
|
|
159
|
+
if (!config.captureResponseBody || chunk == null) {
|
|
160
|
+
logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!meta.response) meta.response = {}
|
|
165
|
+
meta.response.body = safeSerializeBody(chunk)
|
|
166
|
+
logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
request.azifyLogger = {
|
|
170
|
+
startTime,
|
|
171
|
+
method,
|
|
172
|
+
url,
|
|
173
|
+
path,
|
|
174
|
+
responseChunk: null,
|
|
175
|
+
responseChunkCaptured: false,
|
|
176
|
+
logSent: false,
|
|
177
|
+
requestId: null,
|
|
178
|
+
traceId: null,
|
|
179
|
+
spanId: null,
|
|
180
|
+
parentSpanId: null,
|
|
181
|
+
clientIp: null,
|
|
182
|
+
query: null,
|
|
183
|
+
cachedHeaders: null,
|
|
184
|
+
idsCreated: false,
|
|
185
|
+
headersCached: false,
|
|
186
|
+
reqCtx: null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (config.logRequest) {
|
|
190
|
+
process.nextTick(() => {
|
|
191
|
+
ensureIds()
|
|
192
|
+
ensureHeaders()
|
|
193
|
+
|
|
194
|
+
const requestObj = {
|
|
195
|
+
id: requestId,
|
|
196
|
+
method,
|
|
197
|
+
url,
|
|
198
|
+
path,
|
|
199
|
+
ip: clientIp
|
|
200
|
+
}
|
|
201
|
+
if (query) requestObj.query = query
|
|
202
|
+
if (config.captureHeaders && cachedHeaders) requestObj.headers = cachedHeaders
|
|
203
|
+
if (config.captureRequestBody && request.body !== undefined && request.body != null) {
|
|
204
|
+
requestObj.body = safeSerializeBody(request.body)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const meta = {
|
|
208
|
+
traceId: reqCtx.traceId,
|
|
209
|
+
spanId: reqCtx.spanId,
|
|
210
|
+
parentSpanId: reqCtx.parentSpanId || null,
|
|
211
|
+
requestId,
|
|
212
|
+
request: requestObj,
|
|
213
|
+
timestamp: Date.now(),
|
|
214
|
+
hostname
|
|
215
|
+
}
|
|
216
|
+
if (serviceObj) meta.service = serviceObj
|
|
217
|
+
if (config.environment) meta.environment = config.environment
|
|
218
|
+
|
|
219
|
+
logWithContext('info', `[REQUEST] ${method} ${url}`, meta)
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
fastify.addHook('onSend', async (request, reply, payload) => {
|
|
225
|
+
const logger = request.azifyLogger
|
|
226
|
+
if (!logger || logger.logSent) return payload
|
|
227
|
+
|
|
228
|
+
logger.logSent = true
|
|
229
|
+
|
|
230
|
+
if (config.captureResponseBody && payload != null && !logger.responseChunkCaptured) {
|
|
231
|
+
logger.responseChunk = payload
|
|
232
|
+
logger.responseChunkCaptured = true
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const startTime = logger.startTime
|
|
236
|
+
const method = logger.method
|
|
237
|
+
const url = logger.url
|
|
238
|
+
const path = logger.path
|
|
239
|
+
|
|
240
|
+
let requestId, traceId, spanId, parentSpanId, clientIp, query, cachedHeaders
|
|
241
|
+
let idsCreated = false
|
|
242
|
+
let headersCached = false
|
|
243
|
+
let reqCtx = null
|
|
244
|
+
|
|
245
|
+
const logWithContext = (level, message, meta) => {
|
|
246
|
+
if (!reqCtx) {
|
|
247
|
+
const traceHex = sanitizeTraceHex(request.headers['x-trace-id'])
|
|
248
|
+
reqCtx = startRequestContext({
|
|
249
|
+
requestId: requestId || request.requestId || fastUUID(),
|
|
250
|
+
traceHex,
|
|
251
|
+
parentSpanId: request.headers['x-parent-span-id'] || null
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
meta.traceId = meta.traceId || reqCtx.traceId
|
|
255
|
+
meta.spanId = meta.spanId || reqCtx.spanId
|
|
256
|
+
meta.parentSpanId = meta.parentSpanId || reqCtx.parentSpanId
|
|
257
|
+
sendLog(level, message, meta)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function ensureIds() {
|
|
261
|
+
if (idsCreated) return
|
|
262
|
+
idsCreated = true
|
|
263
|
+
|
|
264
|
+
query = request.query || null
|
|
265
|
+
|
|
266
|
+
if (!reqCtx) {
|
|
267
|
+
const traceHex = sanitizeTraceHex(request.headers['x-trace-id'])
|
|
268
|
+
reqCtx = startRequestContext({
|
|
269
|
+
requestId: request.requestId || fastUUID(),
|
|
270
|
+
traceHex,
|
|
271
|
+
parentSpanId: request.headers['x-parent-span-id'] || null
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
requestId = reqCtx.requestId || request.requestId || fastUUID()
|
|
276
|
+
traceId = reqCtx.traceId
|
|
277
|
+
spanId = reqCtx.spanId
|
|
278
|
+
parentSpanId = reqCtx.parentSpanId || request.headers['x-parent-span-id'] || null
|
|
279
|
+
|
|
280
|
+
clientIp = request.ip || request.socket?.remoteAddress || 'unknown'
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function ensureHeaders() {
|
|
284
|
+
if (headersCached) return
|
|
285
|
+
headersCached = true
|
|
286
|
+
if (config.captureHeaders) {
|
|
287
|
+
cachedHeaders = pickHeaders(request.headers || {})
|
|
288
|
+
} else {
|
|
289
|
+
cachedHeaders = {}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function emitResponseLog(meta, chunk) {
|
|
294
|
+
if (!config.captureResponseBody || chunk == null) {
|
|
295
|
+
logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!meta.response) meta.response = {}
|
|
300
|
+
meta.response.body = safeSerializeBody(chunk)
|
|
301
|
+
logWithContext('info', `[RESPONSE] ${method} ${url}`, meta)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
process.nextTick(() => {
|
|
305
|
+
ensureIds()
|
|
306
|
+
|
|
307
|
+
const statusCode = reply.statusCode || 200
|
|
308
|
+
const duration = Date.now() - startTime
|
|
309
|
+
const response = { statusCode, durationMs: duration }
|
|
310
|
+
|
|
311
|
+
if (config.captureHeaders) {
|
|
312
|
+
ensureHeaders()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const requestObj = {
|
|
316
|
+
id: requestId,
|
|
317
|
+
method,
|
|
318
|
+
url,
|
|
319
|
+
path,
|
|
320
|
+
ip: clientIp
|
|
321
|
+
}
|
|
322
|
+
if (query) requestObj.query = query
|
|
323
|
+
if (config.captureHeaders && cachedHeaders) requestObj.headers = cachedHeaders
|
|
324
|
+
|
|
325
|
+
const meta = {
|
|
326
|
+
traceId: reqCtx.traceId,
|
|
327
|
+
spanId: reqCtx.spanId,
|
|
328
|
+
parentSpanId: reqCtx.parentSpanId || null,
|
|
329
|
+
requestId,
|
|
330
|
+
request: requestObj,
|
|
331
|
+
response,
|
|
332
|
+
timestamp: Date.now(),
|
|
333
|
+
hostname
|
|
334
|
+
}
|
|
335
|
+
if (serviceObj) meta.service = serviceObj
|
|
336
|
+
if (config.environment) meta.environment = config.environment
|
|
337
|
+
|
|
338
|
+
const chunkToProcess = (logger.responseChunk !== null && logger.responseChunkCaptured) ? logger.responseChunk : null
|
|
339
|
+
emitResponseLog(meta, chunkToProcess)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
return payload
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
module.exports = createFastifyLoggingPlugin
|
|
348
|
+
|