azify-logger 1.0.29 → 1.0.31
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 +144 -10
- package/middleware-express.js +73 -24
- package/middleware-fastify.js +142 -55
- package/middleware-restify.js +59 -10
- package/package.json +17 -4
- package/register-otel.js +110 -2
- package/register.js +214 -95
- package/scripts/redis-worker.js +37 -11
- package/server.js +132 -5
package/scripts/redis-worker.js
CHANGED
|
@@ -81,6 +81,15 @@ process.on('uncaughtException', (err) => {
|
|
|
81
81
|
})
|
|
82
82
|
|
|
83
83
|
process.on('unhandledRejection', (reason) => {
|
|
84
|
+
const errMsg = String(reason && reason.message ? reason.message : reason)
|
|
85
|
+
const errCode = String(reason && reason.code ? reason.code : '')
|
|
86
|
+
|
|
87
|
+
if (errMsg.includes('BUSYGROUP') ||
|
|
88
|
+
errMsg.includes('Consumer Group name already exists') ||
|
|
89
|
+
errCode === 'BUSYGROUP' ||
|
|
90
|
+
(reason && reason.command && reason.command.name === 'xgroup' && reason.command.args && reason.command.args[0] === 'CREATE' && errMsg.includes('already exists'))) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
84
93
|
console.error('[azify-logger][worker] unhandledRejection:', reason)
|
|
85
94
|
process.exit(1)
|
|
86
95
|
})
|
|
@@ -89,9 +98,16 @@ async function ensureGroup() {
|
|
|
89
98
|
try {
|
|
90
99
|
await redis.xgroup('CREATE', STREAM_KEY, WORKER_GROUP, '0', 'MKSTREAM')
|
|
91
100
|
} catch (err) {
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
const errMsg = String(err && err.message ? err.message : err)
|
|
102
|
+
const errCode = String(err && err.code ? err.code : '')
|
|
103
|
+
|
|
104
|
+
if (errMsg.includes('BUSYGROUP') ||
|
|
105
|
+
errMsg.includes('Consumer Group name already exists') ||
|
|
106
|
+
errCode === 'BUSYGROUP' ||
|
|
107
|
+
(err && err.command && err.command.name === 'xgroup' && err.command.args && err.command.args[0] === 'CREATE' && errMsg.includes('already exists'))) {
|
|
108
|
+
return
|
|
94
109
|
}
|
|
110
|
+
throw err
|
|
95
111
|
}
|
|
96
112
|
}
|
|
97
113
|
|
|
@@ -355,16 +371,17 @@ async function consumeLoop() {
|
|
|
355
371
|
consecutiveNoGroupErrors = 0
|
|
356
372
|
} catch (err) {
|
|
357
373
|
const errMsg = err && err.message ? err.message : String(err)
|
|
358
|
-
if (
|
|
359
|
-
const now = Date.now()
|
|
360
|
-
if (now - lastRedisErrorLog > 5000) {
|
|
361
|
-
console.error('[azify-logger][worker] erro ao garantir grupo:', errMsg)
|
|
362
|
-
lastRedisErrorLog = now
|
|
363
|
-
}
|
|
364
|
-
await sleep(1000)
|
|
365
|
-
} else {
|
|
374
|
+
if (errMsg.includes('BUSYGROUP') || errMsg.includes('Consumer Group name already exists')) {
|
|
366
375
|
groupEnsured = true
|
|
376
|
+
consecutiveNoGroupErrors = 0
|
|
377
|
+
continue
|
|
378
|
+
}
|
|
379
|
+
const now = Date.now()
|
|
380
|
+
if (now - lastRedisErrorLog > 5000) {
|
|
381
|
+
console.error('[azify-logger][worker] erro ao garantir grupo:', errMsg)
|
|
382
|
+
lastRedisErrorLog = now
|
|
367
383
|
}
|
|
384
|
+
await sleep(1000)
|
|
368
385
|
continue
|
|
369
386
|
}
|
|
370
387
|
}
|
|
@@ -434,6 +451,15 @@ ensureGroup()
|
|
|
434
451
|
return consumeLoop()
|
|
435
452
|
})
|
|
436
453
|
.catch((err) => {
|
|
437
|
-
|
|
454
|
+
const errMsg = err && err.message ? err.message : String(err)
|
|
455
|
+
if (errMsg.includes('BUSYGROUP') || errMsg.includes('Consumer Group name already exists')) {
|
|
456
|
+
console.log('[azify-logger][worker] Consumer Group já existe, continuando...')
|
|
457
|
+
console.log('[azify-logger][worker] consumindo stream', STREAM_KEY, 'como', CONSUMER_NAME)
|
|
458
|
+
if (process.send) {
|
|
459
|
+
process.send({ type: 'azify-logger:ready', pid: process.pid, stream: STREAM_KEY })
|
|
460
|
+
}
|
|
461
|
+
return consumeLoop()
|
|
462
|
+
}
|
|
463
|
+
console.error('[azify-logger][worker] não foi possível iniciar:', errMsg)
|
|
438
464
|
process.exit(1)
|
|
439
465
|
})
|
package/server.js
CHANGED
|
@@ -72,7 +72,7 @@ function isPrivateOrLocalhost(ip) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function validateNetworkAccess(req, res, next) {
|
|
75
|
-
if (req.path === '/health' || req.path === '/') {
|
|
75
|
+
if (req.path === '/health' || req.path === '/' || req.path === '/v1/traces') {
|
|
76
76
|
return next()
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -385,6 +385,7 @@ async function setupGrafanaForApp(appName) {
|
|
|
385
385
|
console.log(`[setupGrafana] 📊 Criando datasource para ${appName}...`)
|
|
386
386
|
|
|
387
387
|
const datasourceUid = `opensearch-${appName.toLowerCase()}`
|
|
388
|
+
const indexName = `logs-${appName}`
|
|
388
389
|
const datasourceConfig = {
|
|
389
390
|
name: `OpenSearch-${appName}`,
|
|
390
391
|
type: 'grafana-opensearch-datasource',
|
|
@@ -393,8 +394,8 @@ async function setupGrafanaForApp(appName) {
|
|
|
393
394
|
uid: datasourceUid,
|
|
394
395
|
isDefault: true,
|
|
395
396
|
jsonData: {
|
|
396
|
-
index:
|
|
397
|
-
database:
|
|
397
|
+
index: indexName,
|
|
398
|
+
database: indexName,
|
|
398
399
|
timeField: '@timestamp',
|
|
399
400
|
esVersion: '2.11.1',
|
|
400
401
|
version: '2.11.1',
|
|
@@ -481,6 +482,89 @@ async function setupGrafanaForApp(appName) {
|
|
|
481
482
|
}
|
|
482
483
|
}
|
|
483
484
|
|
|
485
|
+
try {
|
|
486
|
+
const tempoDatasourceUid = `tempo-${appName.toLowerCase()}`
|
|
487
|
+
const tempoUrl = runningInDocker ? 'http://azify-tempo:3200' : 'http://localhost:3200'
|
|
488
|
+
const tempoDatasourceConfig = {
|
|
489
|
+
name: `Tempo-${appName}`,
|
|
490
|
+
type: 'tempo',
|
|
491
|
+
access: 'proxy',
|
|
492
|
+
url: tempoUrl,
|
|
493
|
+
uid: tempoDatasourceUid,
|
|
494
|
+
isDefault: false,
|
|
495
|
+
jsonData: {
|
|
496
|
+
httpMethod: 'GET',
|
|
497
|
+
tracesToLogs: {
|
|
498
|
+
datasourceUid: datasourceUid,
|
|
499
|
+
tags: ['job', 'service', 'pod'],
|
|
500
|
+
mappedTags: [{ key: 'service.name', value: 'service' }],
|
|
501
|
+
mapTagNamesEnabled: false,
|
|
502
|
+
spanStartTimeShift: '1h',
|
|
503
|
+
spanEndTimeShift: '1h',
|
|
504
|
+
filterByTraceID: false,
|
|
505
|
+
filterBySpanID: false
|
|
506
|
+
},
|
|
507
|
+
serviceMap: {
|
|
508
|
+
datasourceUid: datasourceUid
|
|
509
|
+
},
|
|
510
|
+
nodeGraph: {
|
|
511
|
+
enabled: true
|
|
512
|
+
},
|
|
513
|
+
search: {
|
|
514
|
+
hide: false
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
editable: true,
|
|
518
|
+
version: 1
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
console.log(`[setupGrafana] Verificando datasource Tempo existente: ${tempoDatasourceUid} na org ${org.id}`)
|
|
523
|
+
const existingTempo = await axios.get(`${grafanaUrl}/api/datasources/uid/${tempoDatasourceUid}`, {
|
|
524
|
+
auth,
|
|
525
|
+
headers: { 'X-Grafana-Org-Id': org.id },
|
|
526
|
+
timeout: 3000
|
|
527
|
+
})
|
|
528
|
+
console.log(`[setupGrafana] Datasource Tempo existente encontrado: ${existingTempo.data?.id || 'N/A'}`)
|
|
529
|
+
try {
|
|
530
|
+
await axios.put(
|
|
531
|
+
`${grafanaUrl}/api/datasources/${existingTempo.data.id}`,
|
|
532
|
+
tempoDatasourceConfig,
|
|
533
|
+
{
|
|
534
|
+
auth,
|
|
535
|
+
headers: { 'X-Grafana-Org-Id': org.id },
|
|
536
|
+
timeout: 3000
|
|
537
|
+
}
|
|
538
|
+
)
|
|
539
|
+
console.log(`[setupGrafana] ✅ Datasource Tempo atualizado: ${tempoDatasourceUid}`)
|
|
540
|
+
} catch (updateTempoErr) {
|
|
541
|
+
console.error(`[setupGrafana] ⚠️ Erro ao atualizar datasource Tempo: ${updateTempoErr.response?.data?.message || updateTempoErr.message}`)
|
|
542
|
+
}
|
|
543
|
+
} catch (tempoError) {
|
|
544
|
+
console.log(`[setupGrafana] Erro ao verificar datasource Tempo: status=${tempoError.response?.status || 'N/A'}, message=${tempoError.response?.data?.message || tempoError.message}`)
|
|
545
|
+
if (tempoError.response?.status === 404) {
|
|
546
|
+
try {
|
|
547
|
+
const tempoCreate = await axios.post(
|
|
548
|
+
`${grafanaUrl}/api/datasources`,
|
|
549
|
+
tempoDatasourceConfig,
|
|
550
|
+
{
|
|
551
|
+
auth,
|
|
552
|
+
headers: { 'X-Grafana-Org-Id': org.id },
|
|
553
|
+
timeout: 3000
|
|
554
|
+
}
|
|
555
|
+
)
|
|
556
|
+
console.log(`[setupGrafana] ✅ Datasource Tempo criado: ${tempoDatasourceUid} (ID: ${tempoCreate.data?.datasource?.id || 'N/A'})`)
|
|
557
|
+
} catch (createTempoError) {
|
|
558
|
+
console.error(`[setupGrafana] ⚠️ Erro ao criar datasource Tempo via API: ${createTempoError.response?.data?.message || createTempoError.message}`)
|
|
559
|
+
console.log(`[setupGrafana] 💡 Datasource Tempo será criado manualmente ou na próxima tentativa`)
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
} catch (tempoGeneralError) {
|
|
564
|
+
console.error(`[setupGrafana] ⚠️ Erro geral ao configurar datasource Tempo: ${tempoGeneralError.message}`)
|
|
565
|
+
console.log(`[setupGrafana] 💡 Continuando sem datasource Tempo - não é crítico para funcionamento básico`)
|
|
566
|
+
}
|
|
567
|
+
|
|
484
568
|
console.log(`[setupGrafana] 📈 Criando dashboard para ${appName}...`)
|
|
485
569
|
|
|
486
570
|
const appNameLower = appName.toLowerCase()
|
|
@@ -865,6 +949,19 @@ async function handleLog(req, res) {
|
|
|
865
949
|
message = decodeHtmlEntities(message)
|
|
866
950
|
}
|
|
867
951
|
|
|
952
|
+
const requestPath = meta?.request?.path || meta?.request?.url || meta?.request?.baseUrl || meta?.url || meta?.path || ''
|
|
953
|
+
const requestPathLower = String(requestPath).toLowerCase()
|
|
954
|
+
const messageLower = String(message).toLowerCase()
|
|
955
|
+
|
|
956
|
+
const isSwaggerPath = (
|
|
957
|
+
requestPathLower.includes('/api-docs') ||
|
|
958
|
+
requestPathLower.includes('/swagger-ui') ||
|
|
959
|
+
requestPathLower.includes('/swagger.json') ||
|
|
960
|
+
requestPathLower.includes('/swagger.yaml') ||
|
|
961
|
+
requestPathLower.includes('/swagger.yml') ||
|
|
962
|
+
(requestPathLower.includes('/favicon') && (requestPathLower.includes('/api-docs') || requestPathLower.includes('/swagger')))
|
|
963
|
+
)
|
|
964
|
+
|
|
868
965
|
const shouldFilterLog = (
|
|
869
966
|
message.includes('prisma:query') ||
|
|
870
967
|
message.includes('prisma:info') ||
|
|
@@ -874,11 +971,21 @@ async function handleLog(req, res) {
|
|
|
874
971
|
message.includes('UPDATE `') ||
|
|
875
972
|
message.includes('DELETE `') ||
|
|
876
973
|
message.includes('%s: %s') ||
|
|
877
|
-
message.includes('Application Startup Time')
|
|
974
|
+
message.includes('Application Startup Time') ||
|
|
975
|
+
(isSwaggerPath && (
|
|
976
|
+
messageLower.includes('swaggerdoc') ||
|
|
977
|
+
messageLower.includes('"openapi"') ||
|
|
978
|
+
messageLower.includes('swagger-ui-bundle') ||
|
|
979
|
+
messageLower.includes('swagger-ui-standalone') ||
|
|
980
|
+
messageLower.includes('swagger-ui.css') ||
|
|
981
|
+
messageLower.includes('[request]') ||
|
|
982
|
+
messageLower.includes('[response]') ||
|
|
983
|
+
requestPathLower.includes('/favicon')
|
|
984
|
+
))
|
|
878
985
|
)
|
|
879
986
|
|
|
880
987
|
if (shouldFilterLog) {
|
|
881
|
-
return res.json({ success: true, message: 'Log filtrado
|
|
988
|
+
return res.json({ success: true, message: 'Log filtrado' })
|
|
882
989
|
}
|
|
883
990
|
|
|
884
991
|
const requestId = meta && meta.requestId
|
|
@@ -1104,6 +1211,26 @@ async function handleLog(req, res) {
|
|
|
1104
1211
|
}
|
|
1105
1212
|
|
|
1106
1213
|
app.post('/log', (req, res) => handleLog(req, res))
|
|
1214
|
+
|
|
1215
|
+
app.post('/v1/traces', async (req, res) => {
|
|
1216
|
+
try {
|
|
1217
|
+
const collectorUrl = process.env.OTEL_COLLECTOR_URL || 'http://localhost:4318'
|
|
1218
|
+
const response = await axios.post(`${collectorUrl}/v1/traces`, req.body, {
|
|
1219
|
+
headers: {
|
|
1220
|
+
'Content-Type': 'application/json'
|
|
1221
|
+
},
|
|
1222
|
+
data: req.body,
|
|
1223
|
+
timeout: 30000
|
|
1224
|
+
})
|
|
1225
|
+
res.status(response.status).json(response.data)
|
|
1226
|
+
} catch (err) {
|
|
1227
|
+
if (err.response) {
|
|
1228
|
+
res.status(err.response.status).json(err.response.data)
|
|
1229
|
+
} else {
|
|
1230
|
+
res.status(500).json({ error: err.message })
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
})
|
|
1107
1234
|
app.post('/send', (req, res) => handleLog(req, res))
|
|
1108
1235
|
|
|
1109
1236
|
const port = process.env.PORT || 3001
|