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.
@@ -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
- if (!String(err && err.message).includes('BUSYGROUP')) {
93
- throw err
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 (!errMsg.includes('BUSYGROUP')) {
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
- console.error('[azify-logger][worker] não foi possível iniciar:', err)
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: `logs-${appName}`,
397
- database: `logs-${appName}`,
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 (Prisma verboso)' })
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