UNPKG

azify-logger-client-v2

Version:

Azify Logger Client - Centralized logging for OpenSearch

230 lines (200 loc) 6.63 kB
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) })