azify-logger 1.0.25 → 1.0.28
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 +8 -1
- package/index.js +38 -16
- package/middleware-express.js +218 -367
- package/middleware-restify.js +135 -306
- package/package.json +29 -17
- package/queue/fileQueue.js +100 -0
- package/queue/redisQueue.js +181 -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 +467 -0
- package/server.js +168 -70
- package/streams/bunyan.d.ts +26 -0
- package/streams/bunyan.js +39 -8
- package/streams/httpQueue.js +357 -0
- package/streams/pino.d.ts +38 -0
- package/streams/pino.js +44 -7
package/middleware-restify.js
CHANGED
|
@@ -1,348 +1,177 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const pendingLogs = new Set()
|
|
23
|
-
|
|
24
|
-
let exitHandlerInstalled = false
|
|
25
|
-
if (!exitHandlerInstalled) {
|
|
26
|
-
exitHandlerInstalled = true
|
|
27
|
-
|
|
28
|
-
process.on('uncaughtException', (err) => {
|
|
29
|
-
console.error('[AZIFY] Uncaught exception:', err.message)
|
|
30
|
-
|
|
31
|
-
if (pendingLogs.size > 0) {
|
|
32
|
-
setTimeout(() => {
|
|
33
|
-
process.exit(1)
|
|
34
|
-
}, 500)
|
|
35
|
-
} else {
|
|
36
|
-
process.exit(1)
|
|
37
|
-
}
|
|
38
|
-
})
|
|
1
|
+
const { runWithRequestContext, startRequestContext, getRequestContext } = require('./store')
|
|
2
|
+
const { createHttpLoggerTransport } = require('./streams/httpQueue')
|
|
3
|
+
const { randomUUID } = require('crypto')
|
|
4
|
+
const os = require('os')
|
|
5
|
+
|
|
6
|
+
const HEADER_WHITELIST = new Set([
|
|
7
|
+
'content-type',
|
|
8
|
+
'content-length',
|
|
9
|
+
'accept',
|
|
10
|
+
'accept-encoding',
|
|
11
|
+
'user-agent',
|
|
12
|
+
'host',
|
|
13
|
+
'x-request-id',
|
|
14
|
+
'x-trace-id',
|
|
15
|
+
'x-span-id',
|
|
16
|
+
'x-parent-span-id'
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
function pickHeaders (source) {
|
|
20
|
+
if (!source || typeof source !== 'object') {
|
|
21
|
+
return {}
|
|
39
22
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
meta: {
|
|
46
|
-
...meta,
|
|
47
|
-
service: {
|
|
48
|
-
name: config.serviceName,
|
|
49
|
-
version: '1.0.0'
|
|
50
|
-
},
|
|
51
|
-
environment: config.environment,
|
|
52
|
-
timestamp: new Date().toISOString(),
|
|
53
|
-
hostname: require('os').hostname()
|
|
54
|
-
}
|
|
23
|
+
const result = {}
|
|
24
|
+
for (const [key, value] of Object.entries(source)) {
|
|
25
|
+
const lower = key.toLowerCase()
|
|
26
|
+
if (HEADER_WHITELIST.has(lower)) {
|
|
27
|
+
result[key] = Array.isArray(value) ? value.map(String) : (value != null ? String(value) : value)
|
|
55
28
|
}
|
|
56
|
-
|
|
57
|
-
const logId = Math.random().toString(36).substring(7)
|
|
58
|
-
pendingLogs.add(logId)
|
|
59
|
-
|
|
60
|
-
axios.post(`${config.loggerUrl}`, logData, {
|
|
61
|
-
timeout: 2000
|
|
62
|
-
}).then(() => {
|
|
63
|
-
pendingLogs.delete(logId)
|
|
64
|
-
}).catch(() => {
|
|
65
|
-
pendingLogs.delete(logId)
|
|
66
|
-
})
|
|
67
29
|
}
|
|
30
|
+
return result
|
|
31
|
+
}
|
|
68
32
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (sensitiveHeaders.includes(key.toLowerCase())) {
|
|
75
|
-
sanitized[key] = '***'
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return sanitized
|
|
33
|
+
function createRestifyLoggingMiddleware (options = {}) {
|
|
34
|
+
const config = {
|
|
35
|
+
serviceName: options.serviceName || process.env.APP_NAME,
|
|
36
|
+
loggerUrl: options.loggerUrl || process.env.AZIFY_LOGGER_URL || 'http://localhost:3001/log',
|
|
37
|
+
environment: options.environment || process.env.NODE_ENV
|
|
80
38
|
}
|
|
81
39
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
40
|
+
const transport = createHttpLoggerTransport(config.loggerUrl)
|
|
41
|
+
|
|
42
|
+
function sendLog (level, message, meta = {}) {
|
|
43
|
+
try {
|
|
44
|
+
transport.enqueue({
|
|
45
|
+
level,
|
|
46
|
+
message,
|
|
47
|
+
meta: {
|
|
48
|
+
...meta,
|
|
49
|
+
service: {
|
|
50
|
+
name: config.serviceName,
|
|
51
|
+
version: '1.0.0'
|
|
52
|
+
},
|
|
53
|
+
environment: config.environment,
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
hostname: os.hostname()
|
|
56
|
+
}
|
|
57
|
+
}, { 'content-type': 'application/json' })
|
|
58
|
+
} catch (err) { }
|
|
95
59
|
}
|
|
96
60
|
|
|
97
|
-
return function azifyLoggingMiddleware(req, res, next) {
|
|
61
|
+
return function azifyLoggingMiddleware (req, res, next) {
|
|
98
62
|
const startTime = Date.now()
|
|
99
|
-
const requestId = req.requestId ||
|
|
100
|
-
|
|
63
|
+
const requestId = req.requestId || randomUUID()
|
|
64
|
+
|
|
101
65
|
if (res._azifySetup) {
|
|
102
66
|
return next()
|
|
103
67
|
}
|
|
104
68
|
res._azifySetup = true
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
69
|
+
|
|
70
|
+
const currentCtx = getRequestContext()
|
|
71
|
+
const traceId = currentCtx && currentCtx.traceId ? currentCtx.traceId : (req.headers['x-trace-id'] || randomUUID())
|
|
72
|
+
const parentSpanId = currentCtx && currentCtx.spanId ? currentCtx.spanId : (req.headers['x-parent-span-id'] || null)
|
|
73
|
+
const spanId = randomUUID().replace(/-/g, '').slice(0, 16)
|
|
74
|
+
|
|
75
|
+
const reqCtx = startRequestContext({ requestId, traceId, spanId, parentSpanId })
|
|
76
|
+
|
|
77
|
+
let normalizedPath = req.url
|
|
78
|
+
if (normalizedPath.includes('?')) {
|
|
79
|
+
normalizedPath = normalizedPath.slice(0, normalizedPath.indexOf('?'))
|
|
114
80
|
}
|
|
115
|
-
|
|
116
|
-
|
|
81
|
+
normalizedPath = normalizedPath.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '/{id}')
|
|
82
|
+
normalizedPath = normalizedPath.replace(/\/[0-9]+/g, '/{id}')
|
|
117
83
|
|
|
118
|
-
|
|
119
|
-
requestId,
|
|
84
|
+
const requestSnapshot = {
|
|
85
|
+
id: requestId,
|
|
120
86
|
method: req.method,
|
|
121
87
|
url: req.url,
|
|
122
|
-
baseUrl:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
userAgent: req.headers['user-agent'],
|
|
127
|
-
ip: req.connection.remoteAddress || req.socket.remoteAddress,
|
|
128
|
-
traceId: requestTraceId,
|
|
129
|
-
spanId: requestSpanId,
|
|
130
|
-
parentSpanId: requestParentSpanId
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (req.method === 'GET') {
|
|
134
|
-
sendLog('info', `[REQUEST] ${req.method} ${req.url}`, req._azifyRequestData)
|
|
135
|
-
} else {
|
|
136
|
-
if (req.body !== undefined) {
|
|
137
|
-
req._azifyRequestData.requestBody = sanitizeBody(req.body)
|
|
138
|
-
}
|
|
139
|
-
sendLog('info', `[REQUEST] ${req.method} ${req.url}`, req._azifyRequestData)
|
|
88
|
+
baseUrl: normalizedPath,
|
|
89
|
+
headers: pickHeaders(req.headers || {}),
|
|
90
|
+
query: req.query || {},
|
|
91
|
+
ip: req.connection.remoteAddress || req.socket.remoteAddress
|
|
140
92
|
}
|
|
141
93
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
if (arguments.length === 1) {
|
|
150
|
-
sentBody = arguments[0]
|
|
151
|
-
} else if (arguments.length >= 2) {
|
|
152
|
-
sentBody = typeof arguments[0] === 'number' ? arguments[1] : (arguments[1] || arguments[0])
|
|
153
|
-
}
|
|
154
|
-
} catch (_) {}
|
|
155
|
-
|
|
156
|
-
if (!res._azifyResponseLogged) {
|
|
157
|
-
res._azifyResponseLogged = true
|
|
158
|
-
logResponse()
|
|
94
|
+
let responseStatus
|
|
95
|
+
const originalStatus = res.status
|
|
96
|
+
if (typeof originalStatus === 'function') {
|
|
97
|
+
res.status = function patchedStatus (code) {
|
|
98
|
+
if (typeof code === 'number') {
|
|
99
|
+
responseStatus = code
|
|
159
100
|
}
|
|
160
|
-
|
|
161
|
-
return originalSend.apply(this, arguments)
|
|
101
|
+
return originalStatus.call(this, code)
|
|
162
102
|
}
|
|
163
103
|
}
|
|
164
104
|
|
|
165
|
-
|
|
166
|
-
const originalStatus = res.status
|
|
167
|
-
res.status = function(code) {
|
|
168
|
-
res._actualStatusCode = code
|
|
169
|
-
return originalStatus.call(this, code)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
105
|
const originalWriteHead = res.writeHead
|
|
173
|
-
|
|
174
|
-
res.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
statusMessage = undefined
|
|
178
|
-
}
|
|
179
|
-
return originalWriteHead.call(this, statusCode, statusMessage, headers)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const originalJsonMethod = res.json
|
|
183
|
-
res.json = function(code, body) {
|
|
184
|
-
try {
|
|
185
|
-
if (arguments.length === 1) {
|
|
186
|
-
sentBody = arguments[0]
|
|
187
|
-
} else if (arguments.length >= 2) {
|
|
188
|
-
sentBody = typeof arguments[0] === 'number' ? arguments[1] : (arguments[1] || arguments[0])
|
|
106
|
+
if (typeof originalWriteHead === 'function') {
|
|
107
|
+
res.writeHead = function patchedWriteHead (statusCode, statusMessage, headers) {
|
|
108
|
+
if (typeof statusCode === 'number') {
|
|
109
|
+
responseStatus = statusCode
|
|
189
110
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (typeof code === 'number') {
|
|
193
|
-
res._actualStatusCode = code
|
|
194
|
-
} else {
|
|
195
|
-
const errorObj = arguments.length === 1 ? arguments[0] : (typeof arguments[0] === 'number' ? arguments[1] : arguments[0])
|
|
196
|
-
|
|
197
|
-
if (errorObj && errorObj.constructor && errorObj.constructor.name === 'ErrCtor') {
|
|
198
|
-
const errorName = errorObj.toString()
|
|
199
|
-
if (errorName.includes('InternalServerError') || errorName.includes('InternalError')) {
|
|
200
|
-
res._actualStatusCode = 500
|
|
201
|
-
res._actualStatusCode = 500
|
|
202
|
-
} else if (errorName.includes('BadRequest') || errorName.includes('BadDigest')) {
|
|
203
|
-
res._actualStatusCode = 400
|
|
204
|
-
} else if (errorName.includes('NotFound')) {
|
|
205
|
-
res._actualStatusCode = 404
|
|
206
|
-
} else if (errorName.includes('Unauthorized')) {
|
|
207
|
-
res._actualStatusCode = 401
|
|
208
|
-
} else if (errorName.includes('Forbidden')) {
|
|
209
|
-
res._actualStatusCode = 403
|
|
210
|
-
} else {
|
|
211
|
-
res._actualStatusCode = 500
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
res._actualStatusCode = res.statusCode || 200
|
|
111
|
+
if (typeof statusMessage === 'object') {
|
|
112
|
+
headers = statusMessage
|
|
215
113
|
}
|
|
114
|
+
return originalWriteHead.call(this, statusCode, statusMessage, headers)
|
|
216
115
|
}
|
|
217
|
-
return originalJsonMethod.apply(this, arguments)
|
|
218
116
|
}
|
|
219
117
|
|
|
220
|
-
|
|
221
|
-
res.end = function(chunk, encoding) {
|
|
118
|
+
function emitLog (level, message, extraMeta = {}) {
|
|
222
119
|
const duration = Date.now() - startTime
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
logResponse()
|
|
239
|
-
res._azifyResponseLogged = true
|
|
120
|
+
const statusCode = responseStatus || res.statusCode || 200
|
|
121
|
+
const responseHeaders = typeof res.getHeaders === 'function' ? res.getHeaders() : {}
|
|
122
|
+
|
|
123
|
+
const meta = {
|
|
124
|
+
traceId: reqCtx.traceId,
|
|
125
|
+
spanId: reqCtx.spanId,
|
|
126
|
+
parentSpanId: reqCtx.parentSpanId,
|
|
127
|
+
requestId,
|
|
128
|
+
request: requestSnapshot,
|
|
129
|
+
response: {
|
|
130
|
+
statusCode,
|
|
131
|
+
headers: pickHeaders(responseHeaders),
|
|
132
|
+
durationMs: duration
|
|
133
|
+
},
|
|
134
|
+
...extraMeta
|
|
240
135
|
}
|
|
241
136
|
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function logResponse() {
|
|
246
|
-
const duration = Date.now() - startTime
|
|
247
|
-
|
|
248
|
-
let responseBody = sentBody
|
|
249
|
-
|
|
250
|
-
let serializedResponseBody
|
|
251
|
-
try {
|
|
252
|
-
if (typeof responseBody === 'string') {
|
|
253
|
-
serializedResponseBody = responseBody
|
|
254
|
-
} else if (Array.isArray(responseBody)) {
|
|
255
|
-
serializedResponseBody = JSON.stringify(responseBody)
|
|
256
|
-
} else if (responseBody && typeof responseBody === 'object') {
|
|
257
|
-
if (responseBody.toJSON && typeof responseBody.toJSON === 'function') {
|
|
258
|
-
serializedResponseBody = JSON.stringify(responseBody.toJSON())
|
|
259
|
-
} else if (responseBody.toString && typeof responseBody.toString === 'function' && responseBody.toString() !== '[object Object]') {
|
|
260
|
-
serializedResponseBody = responseBody.toString()
|
|
261
|
-
} else {
|
|
262
|
-
serializedResponseBody = JSON.stringify(responseBody, (key, value) => {
|
|
263
|
-
if (typeof value === 'function') {
|
|
264
|
-
return '[Function]'
|
|
265
|
-
}
|
|
266
|
-
if (value instanceof Error) {
|
|
267
|
-
return { name: value.name, message: value.message, stack: value.stack }
|
|
268
|
-
}
|
|
269
|
-
return value
|
|
270
|
-
}, null, 0)
|
|
271
|
-
}
|
|
272
|
-
} else {
|
|
273
|
-
serializedResponseBody = responseBody != null ? String(responseBody) : ''
|
|
274
|
-
}
|
|
275
|
-
} catch (error) {
|
|
276
|
-
try {
|
|
277
|
-
serializedResponseBody = JSON.stringify(responseBody, null, 2)
|
|
278
|
-
} catch (secondError) {
|
|
279
|
-
serializedResponseBody = '[Complex object - serialization failed]'
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const statusCode = res._actualStatusCode || res._statusCode || res.statusCode || 200
|
|
284
|
-
|
|
285
|
-
const responseMessage = serializedResponseBody && serializedResponseBody.length > 0
|
|
286
|
-
? `[RESPONSE] ${serializedResponseBody}`
|
|
287
|
-
: `[RESPONSE] ${req.method} ${req.url} ${statusCode} ${duration}ms`
|
|
288
|
-
|
|
289
|
-
const responseData = {
|
|
290
|
-
...req._azifyRequestData,
|
|
291
|
-
requestBody: req.body,
|
|
292
|
-
statusCode: statusCode,
|
|
293
|
-
responseTime: duration,
|
|
294
|
-
responseHeaders: res.getHeaders ? res.getHeaders() : {},
|
|
295
|
-
responseBody: serializedResponseBody
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
try { res._azifyResponseLogged = true } catch (_) {}
|
|
299
|
-
sendLog('info', responseMessage, responseData)
|
|
137
|
+
sendLog(level, message, meta)
|
|
300
138
|
}
|
|
301
139
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (err && !res._azifyResponseLogged) {
|
|
307
|
-
const errorData = {
|
|
308
|
-
...req._azifyRequestData,
|
|
309
|
-
statusCode: res.statusCode || 500,
|
|
310
|
-
responseTime: Date.now() - startTime,
|
|
311
|
-
error: {
|
|
312
|
-
message: err.message || String(err),
|
|
313
|
-
stack: err.stack,
|
|
314
|
-
name: err.name,
|
|
315
|
-
code: err.code
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
sendLog('error', `[ERROR] ${req.method} ${req.url}: ${err.message || String(err)}`, errorData)
|
|
319
|
-
res._azifyResponseLogged = true
|
|
320
|
-
}
|
|
321
|
-
return originalNext(err)
|
|
140
|
+
function finalize (level = 'info', errorMeta) {
|
|
141
|
+
const message = `${req.method} ${req.url}`
|
|
142
|
+
const meta = errorMeta ? { error: errorMeta } : {}
|
|
143
|
+
emitLog(level, message, meta)
|
|
322
144
|
}
|
|
323
|
-
|
|
324
|
-
res.on('
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
145
|
+
|
|
146
|
+
res.on('finish', () => {
|
|
147
|
+
finalize('info')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
res.on('close', () => {
|
|
151
|
+
finalize('warn', { message: 'response closed before finish' })
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
res.on('error', (err) => {
|
|
155
|
+
finalize('error', {
|
|
156
|
+
message: err && err.message ? err.message : String(err),
|
|
157
|
+
name: err && err.name ? err.name : 'Error',
|
|
158
|
+
stack: err && err.stack ? err.stack : undefined
|
|
159
|
+
})
|
|
339
160
|
})
|
|
340
|
-
|
|
161
|
+
|
|
341
162
|
runWithRequestContext(reqCtx, () => {
|
|
342
|
-
|
|
163
|
+
try {
|
|
164
|
+
next()
|
|
165
|
+
} catch (err) {
|
|
166
|
+
finalize('error', {
|
|
167
|
+
message: err && err.message ? err.message : String(err),
|
|
168
|
+
name: err && err.name ? err.name : 'Error',
|
|
169
|
+
stack: err && err.stack ? err.stack : undefined
|
|
170
|
+
})
|
|
171
|
+
throw err
|
|
172
|
+
}
|
|
343
173
|
})
|
|
344
174
|
}
|
|
345
175
|
}
|
|
346
176
|
|
|
347
177
|
module.exports = createRestifyLoggingMiddleware
|
|
348
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azify-logger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"description": "Azify Logger Client - Centralized logging for OpenSearch",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"pm2:logs": "pm2 logs azify-logger",
|
|
17
17
|
"docker:up": "docker-compose up -d",
|
|
18
18
|
"docker:down": "docker-compose down",
|
|
19
|
-
"docker:logs": "docker-compose logs -f"
|
|
19
|
+
"docker:logs": "docker-compose logs -f",
|
|
20
|
+
"worker": "node scripts/redis-worker.js"
|
|
20
21
|
},
|
|
21
22
|
"keywords": [
|
|
22
23
|
"logging",
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"axios": "^1.6.0",
|
|
32
|
+
"ioredis": "^5.4.1",
|
|
31
33
|
"cors": "^2.8.5",
|
|
32
34
|
"express": "^4.18.2",
|
|
33
35
|
"express-session": "^1.17.3",
|
|
@@ -36,26 +38,33 @@
|
|
|
36
38
|
"js-yaml": "^4.1.0",
|
|
37
39
|
"require-in-the-middle": "^7.4.0",
|
|
38
40
|
"uuid": "^9.0.1",
|
|
39
|
-
"@opentelemetry/
|
|
40
|
-
"@opentelemetry/
|
|
41
|
-
"@opentelemetry/
|
|
42
|
-
|
|
43
|
-
"@opentelemetry/
|
|
44
|
-
"@opentelemetry/
|
|
45
|
-
"@opentelemetry/
|
|
41
|
+
"@opentelemetry/instrumentation-http": "0.27.0",
|
|
42
|
+
"@opentelemetry/instrumentation-express": "0.27.0",
|
|
43
|
+
"@opentelemetry/instrumentation-restify": "0.27.0",
|
|
44
|
+
|
|
45
|
+
"@opentelemetry/api": "1.0.4",
|
|
46
|
+
"@opentelemetry/core": "1.0.1",
|
|
47
|
+
"@opentelemetry/resources": "1.0.1",
|
|
48
|
+
"@opentelemetry/semantic-conventions": "1.0.1",
|
|
49
|
+
"@opentelemetry/sdk-node": "0.27.0",
|
|
50
|
+
"@opentelemetry/exporter-trace-otlp-http": "0.27.0",
|
|
51
|
+
"@opentelemetry/auto-instrumentations-node": "0.27.0"
|
|
46
52
|
},
|
|
47
53
|
"overrides": {
|
|
48
|
-
"@opentelemetry/api": "1.
|
|
49
|
-
"@opentelemetry/core": "1.1
|
|
50
|
-
"@opentelemetry/resources": "1.1
|
|
51
|
-
"@opentelemetry/semantic-conventions": "1.1
|
|
52
|
-
"@opentelemetry/sdk-node": "0.
|
|
53
|
-
"@opentelemetry/exporter-trace-otlp-http": "0.
|
|
54
|
-
"@opentelemetry/auto-instrumentations-node": "0.
|
|
54
|
+
"@opentelemetry/api": "1.0.4",
|
|
55
|
+
"@opentelemetry/core": "1.0.1",
|
|
56
|
+
"@opentelemetry/resources": "1.0.1",
|
|
57
|
+
"@opentelemetry/semantic-conventions": "1.0.1",
|
|
58
|
+
"@opentelemetry/sdk-node": "0.27.0",
|
|
59
|
+
"@opentelemetry/exporter-trace-otlp-http": "0.27.0",
|
|
60
|
+
"@opentelemetry/auto-instrumentations-node": "0.27.0"
|
|
55
61
|
},
|
|
56
62
|
"optionalDependencies": {},
|
|
57
63
|
"engines": {
|
|
58
|
-
"node": ">=12
|
|
64
|
+
"node": ">=12 <=22"
|
|
65
|
+
},
|
|
66
|
+
"bin": {
|
|
67
|
+
"azify-logger-worker": "scripts/redis-worker.js"
|
|
59
68
|
},
|
|
60
69
|
"files": [
|
|
61
70
|
"index.js",
|
|
@@ -69,6 +78,9 @@
|
|
|
69
78
|
"middleware-express.js",
|
|
70
79
|
"middleware-express.d.ts",
|
|
71
80
|
"server.js",
|
|
81
|
+
"sampling.js",
|
|
82
|
+
"queue/",
|
|
83
|
+
"scripts/redis-worker.js",
|
|
72
84
|
"streams/",
|
|
73
85
|
"package.json",
|
|
74
86
|
"README.md"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
function createFileSpool(options = {}) {
|
|
5
|
+
const directory = options.directory || path.join(process.cwd(), '.azify-logger-spool')
|
|
6
|
+
const flushInterval = options.flushInterval || 5000
|
|
7
|
+
const batchSize = options.batchSize || 100
|
|
8
|
+
const pushFn = typeof options.pushFn === 'function' ? options.pushFn : null
|
|
9
|
+
|
|
10
|
+
if (!pushFn) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fs.mkdirSync(directory, { recursive: true })
|
|
15
|
+
|
|
16
|
+
let scheduled = false
|
|
17
|
+
let flushing = false
|
|
18
|
+
|
|
19
|
+
const scheduleFlush = () => {
|
|
20
|
+
if (scheduled) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
scheduled = true
|
|
24
|
+
const timer = setTimeout(async () => {
|
|
25
|
+
scheduled = false
|
|
26
|
+
await flush().catch(() => {})
|
|
27
|
+
}, flushInterval)
|
|
28
|
+
if (typeof timer.unref === 'function') {
|
|
29
|
+
timer.unref()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function append(entry) {
|
|
34
|
+
const filePath = path.join(directory, `spool-${process.pid}.ndjson`)
|
|
35
|
+
await fs.promises.appendFile(filePath, JSON.stringify(entry) + '\n')
|
|
36
|
+
scheduleFlush()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function flush() {
|
|
40
|
+
if (flushing) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
flushing = true
|
|
44
|
+
try {
|
|
45
|
+
const files = (await fs.promises.readdir(directory)).filter((file) => file.endsWith('.ndjson'))
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const fullPath = path.join(directory, file)
|
|
48
|
+
const drainingPath = `${fullPath}.draining`
|
|
49
|
+
try {
|
|
50
|
+
await fs.promises.rename(fullPath, drainingPath)
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err.code === 'ENOENT') {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
throw err
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const content = await fs.promises.readFile(drainingPath, 'utf8').catch(() => '')
|
|
59
|
+
if (!content) {
|
|
60
|
+
await fs.promises.unlink(drainingPath).catch(() => {})
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const lines = content.split('\n').filter(Boolean)
|
|
65
|
+
while (lines.length) {
|
|
66
|
+
const slice = lines.splice(0, batchSize)
|
|
67
|
+
const entries = slice.map((line) => {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(line)
|
|
70
|
+
} catch (_) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
}).filter(Boolean)
|
|
74
|
+
if (entries.length > 0) {
|
|
75
|
+
await pushFn(entries).catch(async () => {
|
|
76
|
+
const remaining = entries.map((entry) => JSON.stringify(entry)).join('\n') + '\n'
|
|
77
|
+
await fs.promises.appendFile(fullPath, remaining)
|
|
78
|
+
throw new Error('Failed to flush file spool entries back to Redis')
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await fs.promises.unlink(drainingPath).catch(() => {})
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
flushing = false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
append,
|
|
92
|
+
flush
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
createFileSpool
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|