azify-logger-client-v2
Version:
Azify Logger Client - Centralized logging for OpenSearch
230 lines (200 loc) • 6.63 kB
JavaScript
require('./register-otel.js')
const axios = require('axios')
const express = require('express')
const cors = require('cors')
const os = require('os')
const { trace, context, propagation } = require('@opentelemetry/api')
const { W3CTraceContextPropagator } = require('@opentelemetry/core')
const app = express()
app.use(express.json())
app.use(cors())
const tracer = trace.getTracer('azify-logger', '1.0.0')
const propagator = new W3CTraceContextPropagator()
const traceContextMap = new Map()
async function ensureIndex() {
const indexName = 'logs-azify'
const osUrl = process.env.OPENSEARCH_URL || 'http://localhost:9200'
try {
await axios.head(`${osUrl}/${indexName}`)
console.log(`✅ Índice ${indexName} já existe no OpenSearch`)
try {
await axios.post(`${osUrl}/${indexName}/_mapping`, {
properties: {
traceId: { type: 'keyword' },
spanId: { type: 'keyword' },
parentSpanId: { type: 'keyword' },
appName: { type: 'keyword' }
}
})
} catch (mapErr) {}
} catch (error) {
if (error.response?.status === 404) {
try {
await axios.put(`${osUrl}/${indexName}`, {
settings: {
number_of_shards: 1,
number_of_replicas: 0
},
mappings: {
properties: {
timestamp: { type: 'date' },
level: { type: 'keyword' },
message: { type: 'text' },
service: {
properties: {
name: { type: 'keyword' },
version: { type: 'keyword' }
}
},
appName: { type: 'keyword' },
traceId: { type: 'keyword' },
spanId: { type: 'keyword' },
parentSpanId: { type: 'keyword' },
userId: { type: 'keyword' },
requestId: { type: 'keyword' },
method: { type: 'keyword' },
url: { type: 'keyword' },
statusCode: { type: 'integer' },
responseTime: { type: 'float' },
ip: { type: 'ip' },
userAgent: { type: 'text' },
environment: { type: 'keyword' },
hostname: { type: 'keyword' },
responseBody: { type: 'text' },
error: {
properties: {
message: { type: 'text' },
stack: { type: 'text' },
name: { type: 'keyword' }
}
}
}
}
})
console.log(`✅ Índice ${indexName} criado no OpenSearch`)
} catch (createError) {
console.error('❌ Erro ao criar índice:', createError.message)
}
}
}
}
ensureIndex()
function generateTraceId() {
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15)
}
function generateSpanId() {
return Math.random().toString(36).substring(2, 15)
}
function getOrCreateTraceContext(requestId) {
if (traceContextMap.has(requestId)) {
return traceContextMap.get(requestId)
}
const traceContext = {
traceId: generateTraceId(),
spanId: generateSpanId(),
parentSpanId: null
}
traceContextMap.set(requestId, traceContext)
setTimeout(() => {
traceContextMap.delete(requestId)
}, 30000)
return traceContext
}
app.get('/health', (req, res) => {
res.json({ status: 'ok', service: 'azify-logger' })
})
app.post('/test-log', async (req, res) => {
const { level, message, meta } = req.body
if (!level || !message) {
return res.status(400).json({ success: false, message: 'Level and message are required.' })
}
const requestId = meta?.requestId
let traceContext = null
if (meta?.traceId && meta?.spanId) {
traceContext = {
traceId: meta.traceId,
spanId: meta.spanId,
parentSpanId: meta.parentSpanId || null
}
} else {
try {
const extractedCtx = propagation.extract(context.active(), req.headers, {
get (carrier, key) {
const header = carrier[key]
if (Array.isArray(header)) return header[0]
return header
},
keys (carrier) {
return Object.keys(carrier)
}
})
const spanContext = trace.getSpan(extractedCtx)?.spanContext?.() || null
if (spanContext && spanContext.traceId && spanContext.spanId) {
traceContext = {
traceId: spanContext.traceId,
spanId: spanContext.spanId,
parentSpanId: null
}
}
} catch (_) {}
}
if (!traceContext && requestId) {
traceContext = getOrCreateTraceContext(requestId)
}
if (!traceContext) {
traceContext = {
traceId: generateTraceId(),
spanId: generateSpanId(),
parentSpanId: null
}
}
const logEntry = {
timestamp: meta?.timestamp || new Date().toISOString(),
level,
message,
service: {
name: meta?.service?.name || 'unknown-service',
version: meta?.service?.version || '1.0.0'
},
appName: meta?.appName || meta?.service?.name || undefined,
environment: meta?.environment || process.env.NODE_ENV || 'development',
hostname: meta?.hostname || os.hostname(),
traceId: traceContext.traceId,
spanId: traceContext.spanId,
parentSpanId: traceContext.parentSpanId
}
if (meta) {
Object.keys(meta).forEach(key => {
if (!['timestamp', 'service', 'environment', 'hostname', 'traceId', 'spanId', 'parentSpanId'].includes(key)) {
logEntry[key] = meta[key]
}
})
}
try {
const osUrl = process.env.OPENSEARCH_URL || 'http://localhost:9200'
await axios.post(`${osUrl}/logs-azify/_doc`, logEntry, {
headers: { 'Content-Type': 'application/json' }
})
console.log(`✅ [${level.toUpperCase()}] ${message} | traceId: ${traceContext.traceId.substring(0, 8)}... | service: ${logEntry.service.name}`)
res.json({ success: true, message: 'Log enviado com sucesso' })
} catch (error) {
console.error('❌ Erro ao enviar log para OpenSearch:', error.message)
if (error.response) {
console.error(' Detalhes:', error.response.data)
}
res.status(500).json({ success: false, message: 'Erro ao enviar log para OpenSearch' })
}
})
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`🚀 Azify Logger rodando na porta ${port}`)
})
process.on('SIGTERM', () => {
console.log('Received SIGTERM, shutting down')
process.exit(0)
})
process.on('SIGINT', () => {
console.log('Received SIGINT, shutting down')
process.exit(0)
})