azify-logger 1.0.29 → 1.0.30

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 CHANGED
@@ -10,15 +10,15 @@ Sistema de logging centralizado com OpenTelemetry e OpenSearch para múltiplas a
10
10
  |----------|-----|
11
11
  | **Development** | `http://localhost:3001/log` |
12
12
  | **Staging** | `https://logsdashboard.azify.dev/send` |
13
- | **Production** | `https://logsdashboard.azify.prd/send` | (a configurar)
13
+ | **Production** | `https://cadence.aztech.host/send` |
14
14
 
15
15
  ### URLs para acessar os logs:
16
16
 
17
- | Ambiente | URL |
18
- |----------|-----|
19
- | **Development** | Grafana: `http://localhost:3002` |
17
+ | Ambiente | URL |
18
+ |----------|-----------------------------------|
19
+ | **Development** | `http://localhost:3002` |
20
20
  | **Staging** | `https://logsdashboard.azify.dev` |
21
- | **Production** | `https://logsdashboard.azify.com` |
21
+ | **Production** | `https://cadence.aztech.host` |
22
22
 
23
23
  ## 📦 Instalação
24
24
 
@@ -68,11 +68,12 @@ await fastify.listen({ port: 3000 })
68
68
 
69
69
  ## ⚙️ Variáveis de Ambiente
70
70
 
71
- | Variável | Padrão | Descrição |
72
- |----------|-------|-----------|
73
- | `APP_NAME` | - | Nome da aplicação |
74
- | `AZIFY_LOGGER_URL` | `http://localhost:3001/log` | URL do logger |
75
- | `NODE_ENV` | `development` | Ambiente |
71
+ | Variável | Padrão | Descrição |
72
+ |----------|------------------------------------|-----------|
73
+ | `APP_NAME` | - | Nome da aplicação |
74
+ | `AZIFY_LOGGER_URL` | `http://localhost:3001/log` | URL do logger |
75
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318/v1/traces` | Endpoint OTLP para traces (opcional) |
76
+ | `NODE_ENV` | `development` | Ambiente |
76
77
 
77
78
  ## 🎯 O Que Você Ganha
78
79
 
@@ -81,6 +82,27 @@ await fastify.listen({ port: 3000 })
81
82
  - ✅ **Trace Consistente**: REQUEST e RESPONSE com mesmo traceId/spanId
82
83
  - ✅ **Genérico**: Funciona com Bunyan, Pino, console.log ou qualquer logger
83
84
  - ✅ **Centralizado**: Todos os logs no OpenSearch
85
+ - ✅ **Tracing Completo**: Traces exportados via OTLP para Grafana Tempo (opcional)
86
+
87
+ ## 📡 OpenTelemetry Collector
88
+
89
+ O `azify-logger` inclui um OpenTelemetry Collector configurado para receber traces via OTLP e exportá-los para Grafana Tempo.
90
+
91
+ ### Configuração
92
+
93
+ Para habilitar o envio de traces, configure a variável de ambiente na sua aplicação:
94
+
95
+ ```bash
96
+ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
97
+ ```
98
+
99
+ ### URLs
100
+
101
+ | Ambiente | URL |
102
+ |----------|-----|
103
+ | **Development** | `http://localhost:4318/v1/traces` |
104
+ | **Staging** | `https://logsdashboard.azify.dev/v1/traces` |
105
+ | **Production** | `https://cadence.aztech.host/v1/traces` |
84
106
 
85
107
  ## 🔍 Como Funciona
86
108
 
@@ -1,9 +1,20 @@
1
- const { startRequestContext } = require('./store')
1
+ const { startRequestContext, runWithRequestContext, getRequestContext } = require('./store')
2
2
  const { createHttpLoggerTransport } = require('./streams/httpQueue')
3
3
  const { Worker } = require('worker_threads')
4
4
  const path = require('path')
5
5
  const os = require('os')
6
6
 
7
+ // Importar OpenTelemetry para sincronizar contexto
8
+ let trace, otelContext
9
+ try {
10
+ const otelApi = require('@opentelemetry/api')
11
+ trace = otelApi.trace
12
+ otelContext = otelApi.context
13
+ } catch (_) {
14
+ trace = { getSpan: () => null }
15
+ otelContext = { active: () => ({}) }
16
+ }
17
+
7
18
  function fastUUID() {
8
19
  const timestamp = Date.now().toString(36)
9
20
  const randomPart = Math.random().toString(36).substring(2, 15)
@@ -159,20 +170,67 @@ function createExpressLoggingMiddleware(options = {}) {
159
170
  let headersCached = false
160
171
  let reqCtx = null
161
172
 
162
- next()
173
+ // Função para extrair traceId/spanId do contexto OTEL ativo
174
+ function getOtelTraceContext() {
175
+ try {
176
+ const activeContext = otelContext.active()
177
+ const span = trace.getSpan(activeContext)
178
+ if (span) {
179
+ const spanContext = span.spanContext()
180
+ if (spanContext && spanContext.traceId && spanContext.spanId) {
181
+ // Converter traceId de hex para formato UUID
182
+ const traceHex = spanContext.traceId.replace(/-/g, '')
183
+ return {
184
+ traceId: traceHex.length === 32 ? `${traceHex.substring(0, 8)}-${traceHex.substring(8, 12)}-${traceHex.substring(12, 16)}-${traceHex.substring(16, 20)}-${traceHex.substring(20, 32)}` : spanContext.traceId,
185
+ spanId: spanContext.spanId,
186
+ parentSpanId: null // OTEL gerencia isso internamente
187
+ }
188
+ }
189
+ }
190
+ } catch (_) {}
191
+ return null
192
+ }
163
193
 
164
- const logWithContext = (level, message, meta) => {
165
- if (!reqCtx) {
166
- const traceHex = sanitizeTraceHex(req.headers['x-trace-id'])
167
- reqCtx = startRequestContext({
168
- requestId: requestId || req.requestId || fastUUID(),
169
- traceHex,
170
- parentSpanId: req.headers['x-parent-span-id'] || null
171
- })
194
+ // Criar contexto inicial sincronizado com OTEL
195
+ function ensureRequestContext() {
196
+ if (reqCtx) return reqCtx
197
+
198
+ // Tentar pegar do OTEL primeiro
199
+ const otelCtx = getOtelTraceContext()
200
+ const traceHex = otelCtx ? otelCtx.traceId.replace(/-/g, '').substring(0, 32) : sanitizeTraceHex(req.headers['x-trace-id'])
201
+
202
+ reqCtx = startRequestContext({
203
+ requestId: req.requestId || fastUUID(),
204
+ traceHex: traceHex || undefined,
205
+ parentSpanId: otelCtx?.parentSpanId || req.headers['x-parent-span-id'] || null
206
+ })
207
+
208
+ // Se OTEL tem contexto, usar os IDs do OTEL
209
+ if (otelCtx) {
210
+ reqCtx.traceId = otelCtx.traceId
211
+ reqCtx.spanId = otelCtx.spanId
172
212
  }
173
- meta.traceId = meta.traceId || reqCtx.traceId
174
- meta.spanId = meta.spanId || reqCtx.spanId
175
- meta.parentSpanId = meta.parentSpanId || reqCtx.parentSpanId
213
+
214
+ return reqCtx
215
+ }
216
+
217
+ // Garantir que o contexto está disponível e sincronizado com OTEL ANTES de processar
218
+ const ctx = ensureRequestContext()
219
+
220
+ // Executar a requisição dentro do contexto AsyncLocalStorage
221
+ // Isso garante que todos os logs intermediários peguem o mesmo traceId
222
+ runWithRequestContext(ctx, () => {
223
+ next()
224
+ })
225
+
226
+ const logWithContext = (level, message, meta) => {
227
+ // Sempre tentar pegar do OTEL primeiro (mais confiável)
228
+ const otelCtx = getOtelTraceContext()
229
+ const ctx = getRequestContext() || ensureRequestContext()
230
+
231
+ meta.traceId = otelCtx?.traceId || meta.traceId || ctx.traceId
232
+ meta.spanId = otelCtx?.spanId || meta.spanId || ctx.spanId
233
+ meta.parentSpanId = otelCtx?.parentSpanId || meta.parentSpanId || ctx.parentSpanId
176
234
  sendLog(level, message, meta)
177
235
  }
178
236
 
@@ -182,19 +240,21 @@ function createExpressLoggingMiddleware(options = {}) {
182
240
 
183
241
  query = req.query || null
184
242
 
185
- if (!reqCtx) {
186
- const traceHex = sanitizeTraceHex(req.headers['x-trace-id'])
187
- reqCtx = startRequestContext({
188
- requestId: req.requestId || fastUUID(),
189
- traceHex,
190
- parentSpanId: req.headers['x-parent-span-id'] || null
191
- })
243
+ const ctx = ensureRequestContext()
244
+
245
+ // Priorizar OTEL se disponível
246
+ const otelCtx = getOtelTraceContext()
247
+ if (otelCtx) {
248
+ traceId = otelCtx.traceId
249
+ spanId = otelCtx.spanId
250
+ parentSpanId = otelCtx.parentSpanId
251
+ } else {
252
+ traceId = ctx.traceId
253
+ spanId = ctx.spanId
254
+ parentSpanId = ctx.parentSpanId || req.headers['x-parent-span-id'] || null
192
255
  }
193
256
 
194
- requestId = reqCtx.requestId || req.requestId || fastUUID()
195
- traceId = reqCtx.traceId
196
- spanId = reqCtx.spanId
197
- parentSpanId = reqCtx.parentSpanId || req.headers['x-parent-span-id'] || null
257
+ requestId = ctx.requestId || req.requestId || fastUUID()
198
258
 
199
259
  clientIp = req.ip || req.connection?.remoteAddress || req.socket?.remoteAddress || 'unknown'
200
260
  req.requestId = requestId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azify-logger",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "description": "Azify Logger Client - Centralized logging for OpenSearch",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -31,7 +31,7 @@
31
31
  "@opentelemetry/api": "1.0.4",
32
32
  "@opentelemetry/auto-instrumentations-node": "0.27.0",
33
33
  "@opentelemetry/core": "1.0.1",
34
- "@opentelemetry/exporter-trace-otlp-http": "0.27.0",
34
+ "@opentelemetry/exporter-trace-otlp-http": "^0.27.0",
35
35
  "@opentelemetry/instrumentation-express": "0.27.0",
36
36
  "@opentelemetry/instrumentation-http": "0.27.0",
37
37
  "@opentelemetry/instrumentation-restify": "0.27.0",
@@ -55,7 +55,6 @@
55
55
  "@opentelemetry/resources": "1.0.1",
56
56
  "@opentelemetry/semantic-conventions": "1.0.1",
57
57
  "@opentelemetry/sdk-node": "0.27.0",
58
- "@opentelemetry/exporter-trace-otlp-http": "0.27.0",
59
58
  "@opentelemetry/auto-instrumentations-node": "0.27.0"
60
59
  },
61
60
  "engines": {
package/register-otel.js CHANGED
@@ -3,6 +3,8 @@ try {
3
3
  const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
4
4
  const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express')
5
5
  const { RestifyInstrumentation } = require('@opentelemetry/instrumentation-restify')
6
+ const { Resource } = require('@opentelemetry/resources')
7
+ const { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } = require('@opentelemetry/semantic-conventions')
6
8
 
7
9
  const serviceName = process.env.OTEL_SERVICE_NAME || process.env.APP_NAME || 'app'
8
10
  const serviceVersion = process.env.OTEL_SERVICE_VERSION || '1.0.0'
@@ -10,6 +12,12 @@ try {
10
12
  process.env.OTEL_SERVICE_NAME = serviceName
11
13
  process.env.OTEL_SERVICE_VERSION = serviceVersion
12
14
 
15
+ const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
16
+ if (otlpEndpoint) {
17
+ process.env.OTEL_EXPORTER_OTLP_ENDPOINT = otlpEndpoint
18
+ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = otlpEndpoint
19
+ }
20
+
13
21
  let collectorHost = null
14
22
  let collectorPath = null
15
23
  try {
@@ -62,9 +70,109 @@ try {
62
70
  enabled: true
63
71
  })
64
72
 
73
+ let traceExporter = null
74
+ if (otlpEndpoint) {
75
+ try {
76
+ let endpointUrl
77
+ try {
78
+ endpointUrl = new URL(otlpEndpoint)
79
+ } catch (urlErr) {
80
+ throw new Error(`URL inválida: ${otlpEndpoint}`)
81
+ }
82
+
83
+ const http = require('http')
84
+ const url = new URL(otlpEndpoint)
85
+
86
+ traceExporter = {
87
+ export: function(spans, resultCallback) {
88
+ if (!spans || spans.length === 0) {
89
+ if (resultCallback) resultCallback({ code: 0 })
90
+ return
91
+ }
92
+
93
+ try {
94
+ const resourceSpans = [{
95
+ resource: {
96
+ attributes: [
97
+ { key: 'service.name', value: { stringValue: serviceName } },
98
+ { key: 'service.version', value: { stringValue: serviceVersion } }
99
+ ]
100
+ },
101
+ scopeSpans: [{
102
+ spans: spans.map(span => {
103
+ const ctx = span.spanContext()
104
+ return {
105
+ traceId: ctx.traceId.replace(/-/g, '').padStart(32, '0').substring(0, 32),
106
+ spanId: ctx.spanId.replace(/-/g, '').padStart(16, '0').substring(0, 16),
107
+ name: span.name || 'span',
108
+ kind: span.kind || 1,
109
+ startTimeUnixNano: String(span.startTime?.[0] * 1e9 + (span.startTime?.[1] || 0) || Date.now() * 1e6),
110
+ endTimeUnixNano: String(span.endTime?.[0] * 1e9 + (span.endTime?.[1] || 0) || Date.now() * 1e6),
111
+ attributes: Object.entries(span.attributes || {}).map(([k, v]) => ({
112
+ key: k,
113
+ value: { stringValue: String(v) }
114
+ }))
115
+ }
116
+ })
117
+ }]
118
+ }]
119
+
120
+ const payload = JSON.stringify({ resourceSpans })
121
+
122
+ const options = {
123
+ hostname: url.hostname,
124
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
125
+ path: url.pathname,
126
+ method: 'POST',
127
+ headers: {
128
+ 'Content-Type': 'application/json',
129
+ 'Content-Length': Buffer.byteLength(payload)
130
+ }
131
+ }
132
+
133
+ const req = http.request(options, (res) => {
134
+ let data = ''
135
+ res.on('data', (chunk) => { data += chunk })
136
+ res.on('end', () => {
137
+ if (res.statusCode >= 200 && res.statusCode < 300) {
138
+ if (resultCallback) resultCallback({ code: 0 })
139
+ } else {
140
+ if (resultCallback) resultCallback({ code: 1, error: new Error(`HTTP ${res.statusCode}`) })
141
+ }
142
+ })
143
+ })
144
+
145
+ req.on('error', (err) => {
146
+ if (resultCallback) resultCallback({ code: 1, error: err })
147
+ })
148
+
149
+ req.write(payload)
150
+ req.end()
151
+ } catch (err) {
152
+ if (resultCallback) resultCallback({ code: 1, error: err })
153
+ }
154
+ },
155
+ shutdown: function() {
156
+ return Promise.resolve()
157
+ }
158
+ }
159
+
160
+ } catch (err) {
161
+ }
162
+ }
163
+
164
+ let spanProcessor = null
165
+ if (traceExporter) {
166
+ const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base')
167
+ spanProcessor = new SimpleSpanProcessor(traceExporter)
168
+ }
169
+
65
170
  const sdk = new NodeSDK({
66
- serviceName,
67
- serviceVersion,
171
+ resource: new Resource({
172
+ [SEMRESATTRS_SERVICE_NAME]: serviceName,
173
+ [SEMRESATTRS_SERVICE_VERSION]: serviceVersion
174
+ }),
175
+ spanProcessor,
68
176
  instrumentations: [
69
177
  httpInstrumentation,
70
178
  expressInstrumentation,
package/register.js CHANGED
@@ -175,14 +175,17 @@ try {
175
175
 
176
176
  logger.info = function(obj, msg, ...args) {
177
177
  const ctx = getRequestContext()
178
- if (ctx && ctx.traceId) {
178
+ const otelCtx = !ctx || !ctx.traceId ? getOtelTraceContext() : null
179
+ const traceCtx = ctx || otelCtx
180
+
181
+ if (traceCtx && traceCtx.traceId) {
179
182
  if (typeof obj === 'object' && obj !== null) {
180
- obj.traceId = ctx.traceId
181
- obj.spanId = ctx.spanId
182
- obj.parentSpanId = ctx.parentSpanId
183
- obj.requestId = ctx.requestId
183
+ obj.traceId = traceCtx.traceId
184
+ obj.spanId = traceCtx.spanId
185
+ obj.parentSpanId = traceCtx.parentSpanId
186
+ obj.requestId = traceCtx.requestId || ctx?.requestId
184
187
  } else {
185
- obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
188
+ obj = { traceId: traceCtx.traceId, spanId: traceCtx.spanId, parentSpanId: traceCtx.parentSpanId, requestId: traceCtx.requestId || ctx?.requestId, msg: obj }
186
189
  }
187
190
  }
188
191
  return originalInfo.call(this, obj, msg, ...args)
@@ -190,14 +193,17 @@ try {
190
193
 
191
194
  logger.error = function(obj, msg, ...args) {
192
195
  const ctx = getRequestContext()
193
- if (ctx && ctx.traceId) {
196
+ const otelCtx = !ctx || !ctx.traceId ? getOtelTraceContext() : null
197
+ const traceCtx = ctx || otelCtx
198
+
199
+ if (traceCtx && traceCtx.traceId) {
194
200
  if (typeof obj === 'object' && obj !== null) {
195
- obj.traceId = ctx.traceId
196
- obj.spanId = ctx.spanId
197
- obj.parentSpanId = ctx.parentSpanId
198
- obj.requestId = ctx.requestId
201
+ obj.traceId = traceCtx.traceId
202
+ obj.spanId = traceCtx.spanId
203
+ obj.parentSpanId = traceCtx.parentSpanId
204
+ obj.requestId = traceCtx.requestId || ctx?.requestId
199
205
  } else {
200
- obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
206
+ obj = { traceId: traceCtx.traceId, spanId: traceCtx.spanId, parentSpanId: traceCtx.parentSpanId, requestId: traceCtx.requestId || ctx?.requestId, msg: obj }
201
207
  }
202
208
  }
203
209
  return originalError.call(this, obj, msg, ...args)
@@ -205,14 +211,17 @@ try {
205
211
 
206
212
  logger.warn = function(obj, msg, ...args) {
207
213
  const ctx = getRequestContext()
208
- if (ctx && ctx.traceId) {
214
+ const otelCtx = !ctx || !ctx.traceId ? getOtelTraceContext() : null
215
+ const traceCtx = ctx || otelCtx
216
+
217
+ if (traceCtx && traceCtx.traceId) {
209
218
  if (typeof obj === 'object' && obj !== null) {
210
- obj.traceId = ctx.traceId
211
- obj.spanId = ctx.spanId
212
- obj.parentSpanId = ctx.parentSpanId
213
- obj.requestId = ctx.requestId
219
+ obj.traceId = traceCtx.traceId
220
+ obj.spanId = traceCtx.spanId
221
+ obj.parentSpanId = traceCtx.parentSpanId
222
+ obj.requestId = traceCtx.requestId || ctx?.requestId
214
223
  } else {
215
- obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
224
+ obj = { traceId: traceCtx.traceId, spanId: traceCtx.spanId, parentSpanId: traceCtx.parentSpanId, requestId: traceCtx.requestId || ctx?.requestId, msg: obj }
216
225
  }
217
226
  }
218
227
  return originalWarn.call(this, obj, msg, ...args)
@@ -220,14 +229,17 @@ try {
220
229
 
221
230
  logger.debug = function(obj, msg, ...args) {
222
231
  const ctx = getRequestContext()
223
- if (ctx && ctx.traceId) {
232
+ const otelCtx = !ctx || !ctx.traceId ? getOtelTraceContext() : null
233
+ const traceCtx = ctx || otelCtx
234
+
235
+ if (traceCtx && traceCtx.traceId) {
224
236
  if (typeof obj === 'object' && obj !== null) {
225
- obj.traceId = ctx.traceId
226
- obj.spanId = ctx.spanId
227
- obj.parentSpanId = ctx.parentSpanId
228
- obj.requestId = ctx.requestId
237
+ obj.traceId = traceCtx.traceId
238
+ obj.spanId = traceCtx.spanId
239
+ obj.parentSpanId = traceCtx.parentSpanId
240
+ obj.requestId = traceCtx.requestId || ctx?.requestId
229
241
  } else {
230
- obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
242
+ obj = { traceId: traceCtx.traceId, spanId: traceCtx.spanId, parentSpanId: traceCtx.parentSpanId, requestId: traceCtx.requestId || ctx?.requestId, msg: obj }
231
243
  }
232
244
  }
233
245
  return originalDebug.call(this, obj, msg, ...args)
@@ -235,14 +247,17 @@ try {
235
247
 
236
248
  logger.fatal = function(obj, msg, ...args) {
237
249
  const ctx = getRequestContext()
238
- if (ctx && ctx.traceId) {
250
+ const otelCtx = !ctx || !ctx.traceId ? getOtelTraceContext() : null
251
+ const traceCtx = ctx || otelCtx
252
+
253
+ if (traceCtx && traceCtx.traceId) {
239
254
  if (typeof obj === 'object' && obj !== null) {
240
- obj.traceId = ctx.traceId
241
- obj.spanId = ctx.spanId
242
- obj.parentSpanId = ctx.parentSpanId
243
- obj.requestId = ctx.requestId
255
+ obj.traceId = traceCtx.traceId
256
+ obj.spanId = traceCtx.spanId
257
+ obj.parentSpanId = traceCtx.parentSpanId
258
+ obj.requestId = traceCtx.requestId || ctx?.requestId
244
259
  } else {
245
- obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
260
+ obj = { traceId: traceCtx.traceId, spanId: traceCtx.spanId, parentSpanId: traceCtx.parentSpanId, requestId: traceCtx.requestId || ctx?.requestId, msg: obj }
246
261
  }
247
262
  }
248
263
  return originalFatal.call(this, obj, msg, ...args)
@@ -250,14 +265,17 @@ try {
250
265
 
251
266
  logger.trace = function(obj, msg, ...args) {
252
267
  const ctx = getRequestContext()
253
- if (ctx && ctx.traceId) {
268
+ const otelCtx = !ctx || !ctx.traceId ? getOtelTraceContext() : null
269
+ const traceCtx = ctx || otelCtx
270
+
271
+ if (traceCtx && traceCtx.traceId) {
254
272
  if (typeof obj === 'object' && obj !== null) {
255
- obj.traceId = ctx.traceId
256
- obj.spanId = ctx.spanId
257
- obj.parentSpanId = ctx.parentSpanId
258
- obj.requestId = ctx.requestId
273
+ obj.traceId = traceCtx.traceId
274
+ obj.spanId = traceCtx.spanId
275
+ obj.parentSpanId = traceCtx.parentSpanId
276
+ obj.requestId = traceCtx.requestId || ctx?.requestId
259
277
  } else {
260
- obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
278
+ obj = { traceId: traceCtx.traceId, spanId: traceCtx.spanId, parentSpanId: traceCtx.parentSpanId, requestId: traceCtx.requestId || ctx?.requestId, msg: obj }
261
279
  }
262
280
  }
263
281
  return originalTrace.call(this, obj, msg, ...args)
package/server.js CHANGED
@@ -481,6 +481,90 @@ async function setupGrafanaForApp(appName) {
481
481
  }
482
482
  }
483
483
 
484
+ // Datasource Grafana Tempo para tracing completo (opcional - não bloqueia criação da org)
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()