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 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
 
@@ -183,6 +205,22 @@ curl -X POST http://localhost:3001/log \
183
205
 
184
206
  Acesse `http://localhost:3002` e faça login com Azure AD.
185
207
 
208
+ ### Apps fora da mesma máquina / rede (Azure)
209
+
210
+ Por padrão, o `azify-logger` **só aceita chamadas dos próprios serviços locais** (mesma VM / rede privada).
211
+
212
+ Se sua aplicação **não estiver rodando na mesma máquina/VM** do `azify-logger`:
213
+
214
+ - Descubra o **IP público** da aplicação cliente
215
+ - Envie esse IP para que ele seja incluído na configuração interna (`ALLOWED_SOURCE_IPS`)
216
+
217
+ Isso vale tanto para:
218
+
219
+ - Envio de logs (`/log` e `/send`)
220
+ - Envio de traces (`/v1/traces`)
221
+
222
+ Enquanto o IP não for incluído nessa lista, chamadas vindas de fora da mesma máquina/rede serão bloqueadas com `403 Forbidden`.
223
+
186
224
  ## 🚀 Deploy
187
225
 
188
226
  ### Staging - Deploy Automático
@@ -200,6 +238,102 @@ O deploy é feito automaticamente via GitHub Actions.
200
238
  3. Configure: **Environment:** `production`
201
239
  4. Clique em **Run workflow**
202
240
 
241
+ ## 📦 Retenção e Arquivamento de Logs
242
+
243
+ > **⚠️ Importante**: A retenção é configurada **apenas no servidor `azify-logger`**. As aplicações que usam a lib **não precisam se preocupar** com retenção - elas apenas enviam logs normalmente.
244
+
245
+ O `azify-logger` inclui um sistema automático de retenção que:
246
+
247
+ - ✅ **Mantém sempre os últimos 30 dias** de logs disponíveis no OpenSearch para consulta imediata
248
+ - ✅ **Compacta e arquiva** logs mais antigos automaticamente
249
+ - ✅ **Envia para Azure Blob Storage** para armazenamento de longo prazo
250
+ - ✅ **Remove logs antigos** do OpenSearch após arquivamento bem-sucedido
251
+
252
+ ### Como Funciona
253
+
254
+ 1. **Agendamento**: O serviço de retenção executa automaticamente 1x por dia às 3h da manhã (configurável via `RETENTION_RUN_AT_HOUR`)
255
+ 2. **Identificação**: Busca todos os logs com mais de 30 dias no OpenSearch
256
+ 3. **Arquivamento**:
257
+ - Exporta logs mais antigos que 30 dias
258
+ - Compacta em formato ZIP
259
+ - Envia para Azure Blob Storage na estrutura `logs/{app}/{YYYYMM}/{DD}/`
260
+ - Remove do OpenSearch após confirmação de upload bem-sucedido
261
+
262
+ ### Configuração (Apenas no Servidor azify-logger)
263
+
264
+ A configuração de retenção é feita **apenas no servidor `azify-logger`**, no arquivo `env/app.env`:
265
+
266
+ ```bash
267
+ RETENTION_DAYS=30 # Dias de logs mantidos no OpenSearch (padrão: 30)
268
+ RETENTION_RUN_AT_HOUR=3 # Horário de execução diária (0-23, padrão: 3h da manhã)
269
+
270
+ **⚠️ As aplicações que usam a lib `azify-logger` não precisam configurar nada relacionado à retenção.**
271
+
272
+ ### Execução Manual
273
+
274
+ Para executar a retenção manualmente (útil para testes):
275
+
276
+ ```bash
277
+ # Executar uma vez
278
+ npm run retention -- --once
279
+
280
+ # Ou via Docker
281
+ docker exec azify-retention-manager node scripts/retention-manager.js --once
282
+ ```
283
+
284
+ ### Estrutura no Blob Storage
285
+
286
+ Os arquivos são organizados da seguinte forma:
287
+
288
+ ```
289
+ {container}/
290
+ └── logs/
291
+ └── {YYYYMM}/ (ex: 202512/)
292
+ └── log-YYYY-MM-DD-HH-MM-SS-UUID.zip
293
+ ```
294
+
295
+ Cada arquivo ZIP contém:
296
+ - `metadata.json`: Metadados do índice (nome, data de exportação, contagem de documentos, dias de retenção)
297
+ - `{indexName}-{timestamp}.json`: Todos os documentos exportados em JSON
298
+
299
+ O sistema sempre mantém **os últimos 30 dias** de logs disponíveis no OpenSearch/Grafana.
300
+
301
+ ### Scripts Úteis
302
+
303
+ #### Consulta e Validação de Blob Storage
304
+
305
+ ```bash
306
+ # Listar todos os arquivos no blob storage
307
+ npm run list:blob
308
+
309
+ # Listar arquivos ZIP recentes (última hora)
310
+ npm run list:blob:recent
311
+
312
+ # Baixar um arquivo ZIP específico
313
+ npm run download:blob "logs/harmony/202512/05/log-2025-12-05-14-29-01.zip" ~/Downloads
314
+
315
+ # Baixar todos os arquivos de uma pasta
316
+ npm run download:blob "logs/harmony/202512/05/*" ~/Downloads
317
+ ```
318
+
319
+ #### Reimportação de Logs
320
+
321
+ Para reimportar logs arquivados de volta para o OpenSearch (útil para análise de logs antigos):
322
+
323
+ ```bash
324
+ # Reimportar um arquivo ZIP específico
325
+ npm run reimport:logs ~/Downloads/log-2025-12-05-18-31-32.zip
326
+
327
+ # Reimportar todos os ZIPs de uma pasta
328
+ npm run reimport:logs ~/Downloads/arquivos-exportados/
329
+ ```
330
+
331
+ **Importante:**
332
+ - Os logs serão reimportados no índice original
333
+ - Os timestamps originais são preservados
334
+ - A aplicação já deve estar configurada no Grafana (organização e datasource)
335
+ - Após a reimportação, os logs estarão disponíveis no Grafana imediatamente
336
+
203
337
  ## ⚠️ Preservação de Dados
204
338
 
205
339
  **IMPORTANTE:** Logs do OpenSearch são armazenados em volume Docker persistente.
@@ -1,9 +1,19 @@
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
+ let trace, otelContext
8
+ try {
9
+ const otelApi = require('@opentelemetry/api')
10
+ trace = otelApi.trace
11
+ otelContext = otelApi.context
12
+ } catch (_) {
13
+ trace = { getSpan: () => null }
14
+ otelContext = { active: () => ({}) }
15
+ }
16
+
7
17
  function fastUUID() {
8
18
  const timestamp = Date.now().toString(36)
9
19
  const randomPart = Math.random().toString(36).substring(2, 15)
@@ -159,20 +169,58 @@ function createExpressLoggingMiddleware(options = {}) {
159
169
  let headersCached = false
160
170
  let reqCtx = null
161
171
 
162
- next()
172
+ function getOtelTraceContext() {
173
+ try {
174
+ const activeContext = otelContext.active()
175
+ const span = trace.getSpan(activeContext)
176
+ if (span) {
177
+ const spanContext = span.spanContext()
178
+ if (spanContext && spanContext.traceId && spanContext.spanId) {
179
+ const traceHex = spanContext.traceId.replace(/-/g, '')
180
+ return {
181
+ 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,
182
+ spanId: spanContext.spanId,
183
+ parentSpanId: null
184
+ }
185
+ }
186
+ }
187
+ } catch (_) {}
188
+ return null
189
+ }
163
190
 
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
- })
191
+ function ensureRequestContext() {
192
+ if (reqCtx) return reqCtx
193
+
194
+ const otelCtx = getOtelTraceContext()
195
+ const traceHex = otelCtx ? otelCtx.traceId.replace(/-/g, '').substring(0, 32) : sanitizeTraceHex(req.headers['x-trace-id'])
196
+
197
+ reqCtx = startRequestContext({
198
+ requestId: req.requestId || fastUUID(),
199
+ traceHex: traceHex || undefined,
200
+ parentSpanId: otelCtx?.parentSpanId || req.headers['x-parent-span-id'] || null
201
+ })
202
+
203
+ if (otelCtx) {
204
+ reqCtx.traceId = otelCtx.traceId
205
+ reqCtx.spanId = otelCtx.spanId
172
206
  }
173
- meta.traceId = meta.traceId || reqCtx.traceId
174
- meta.spanId = meta.spanId || reqCtx.spanId
175
- meta.parentSpanId = meta.parentSpanId || reqCtx.parentSpanId
207
+
208
+ return reqCtx
209
+ }
210
+
211
+ const ctx = ensureRequestContext()
212
+
213
+ runWithRequestContext(ctx, () => {
214
+ next()
215
+ })
216
+
217
+ const logWithContext = (level, message, meta) => {
218
+ const otelCtx = getOtelTraceContext()
219
+ const ctx = getRequestContext() || ensureRequestContext()
220
+
221
+ meta.traceId = otelCtx?.traceId || meta.traceId || ctx.traceId
222
+ meta.spanId = otelCtx?.spanId || meta.spanId || ctx.spanId
223
+ meta.parentSpanId = otelCtx?.parentSpanId || meta.parentSpanId || ctx.parentSpanId
176
224
  sendLog(level, message, meta)
177
225
  }
178
226
 
@@ -182,19 +230,20 @@ function createExpressLoggingMiddleware(options = {}) {
182
230
 
183
231
  query = req.query || null
184
232
 
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
- })
233
+ const ctx = ensureRequestContext()
234
+
235
+ const otelCtx = getOtelTraceContext()
236
+ if (otelCtx) {
237
+ traceId = otelCtx.traceId
238
+ spanId = otelCtx.spanId
239
+ parentSpanId = otelCtx.parentSpanId
240
+ } else {
241
+ traceId = ctx.traceId
242
+ spanId = ctx.spanId
243
+ parentSpanId = ctx.parentSpanId || req.headers['x-parent-span-id'] || null
192
244
  }
193
245
 
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
246
+ requestId = ctx.requestId || req.requestId || fastUUID()
198
247
 
199
248
  clientIp = req.ip || req.connection?.remoteAddress || req.socket?.remoteAddress || 'unknown'
200
249
  req.requestId = requestId
@@ -1,7 +1,17 @@
1
- const { startRequestContext } = require('./store')
1
+ const { startRequestContext, runWithRequestContext, getRequestContext } = require('./store')
2
2
  const { createHttpLoggerTransport } = require('./streams/httpQueue')
3
3
  const os = require('os')
4
4
 
5
+ let trace, otelContext
6
+ try {
7
+ const otelApi = require('@opentelemetry/api')
8
+ trace = otelApi.trace
9
+ otelContext = otelApi.context
10
+ } catch (_) {
11
+ trace = { getSpan: () => null }
12
+ otelContext = { active: () => ({}) }
13
+ }
14
+
5
15
  function fastUUID() {
6
16
  const timestamp = Date.now().toString(36)
7
17
  const randomPart = Math.random().toString(36).substring(2, 15)
@@ -92,6 +102,50 @@ function createFastifyLoggingPlugin(options = {}) {
92
102
 
93
103
  return async function azifyFastifyPlugin(fastify, opts) {
94
104
  fastify.addHook('onRequest', async (request, reply) => {
105
+ let reqCtx = null
106
+
107
+ function getOtelTraceContext() {
108
+ try {
109
+ const activeContext = otelContext.active()
110
+ const span = trace.getSpan(activeContext)
111
+ if (span) {
112
+ const spanContext = span.spanContext()
113
+ if (spanContext && spanContext.traceId && spanContext.spanId) {
114
+ const traceHex = spanContext.traceId.replace(/-/g, '')
115
+ return {
116
+ 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,
117
+ spanId: spanContext.spanId,
118
+ parentSpanId: null
119
+ }
120
+ }
121
+ }
122
+ } catch (_) {}
123
+ return null
124
+ }
125
+
126
+ function ensureRequestContext() {
127
+ if (reqCtx) return reqCtx
128
+
129
+ const otelCtx = getOtelTraceContext()
130
+ const traceHex = otelCtx ? otelCtx.traceId.replace(/-/g, '').substring(0, 32) : sanitizeTraceHex(request.headers['x-trace-id'])
131
+
132
+ reqCtx = startRequestContext({
133
+ requestId: request.requestId || fastUUID(),
134
+ traceHex: traceHex || undefined,
135
+ parentSpanId: otelCtx?.parentSpanId || request.headers['x-parent-span-id'] || null
136
+ })
137
+
138
+ if (otelCtx) {
139
+ reqCtx.traceId = otelCtx.traceId
140
+ reqCtx.spanId = otelCtx.spanId
141
+ }
142
+
143
+ return reqCtx
144
+ }
145
+
146
+ const ctx = ensureRequestContext()
147
+
148
+ runWithRequestContext(ctx, () => {})
95
149
  const startTime = Date.now()
96
150
  const method = request.method
97
151
  const url = request.url
@@ -104,20 +158,14 @@ function createFastifyLoggingPlugin(options = {}) {
104
158
  let requestId, traceId, spanId, parentSpanId, clientIp, query, cachedHeaders
105
159
  let idsCreated = false
106
160
  let headersCached = false
107
- let reqCtx = null
108
161
 
109
162
  const logWithContext = (level, message, meta) => {
110
- if (!reqCtx) {
111
- const traceHex = sanitizeTraceHex(request.headers['x-trace-id'])
112
- reqCtx = startRequestContext({
113
- requestId: requestId || request.requestId || fastUUID(),
114
- traceHex,
115
- parentSpanId: request.headers['x-parent-span-id'] || null
116
- })
117
- }
118
- meta.traceId = meta.traceId || reqCtx.traceId
119
- meta.spanId = meta.spanId || reqCtx.spanId
120
- meta.parentSpanId = meta.parentSpanId || reqCtx.parentSpanId
163
+ const otelCtx = getOtelTraceContext()
164
+ const ctx = getRequestContext() || ensureRequestContext()
165
+
166
+ meta.traceId = otelCtx?.traceId || meta.traceId || ctx.traceId
167
+ meta.spanId = otelCtx?.spanId || meta.spanId || ctx.spanId
168
+ meta.parentSpanId = otelCtx?.parentSpanId || meta.parentSpanId || ctx.parentSpanId
121
169
  sendLog(level, message, meta)
122
170
  }
123
171
 
@@ -127,20 +175,20 @@ function createFastifyLoggingPlugin(options = {}) {
127
175
 
128
176
  query = request.query || null
129
177
 
130
- if (!reqCtx) {
131
- const traceHex = sanitizeTraceHex(request.headers['x-trace-id'])
132
- reqCtx = startRequestContext({
133
- requestId: request.requestId || fastUUID(),
134
- traceHex,
135
- parentSpanId: request.headers['x-parent-span-id'] || null
136
- })
137
- }
178
+ const ctx = ensureRequestContext()
138
179
 
139
- requestId = reqCtx.requestId || request.requestId || fastUUID()
140
- traceId = reqCtx.traceId
141
- spanId = reqCtx.spanId
142
- parentSpanId = reqCtx.parentSpanId || request.headers['x-parent-span-id'] || null
180
+ const otelCtx = getOtelTraceContext()
181
+ if (otelCtx) {
182
+ traceId = otelCtx.traceId
183
+ spanId = otelCtx.spanId
184
+ parentSpanId = otelCtx.parentSpanId
185
+ } else {
186
+ traceId = ctx.traceId
187
+ spanId = ctx.spanId
188
+ parentSpanId = ctx.parentSpanId || request.headers['x-parent-span-id'] || null
189
+ }
143
190
 
191
+ requestId = ctx.requestId || request.requestId || fastUUID()
144
192
  clientIp = request.ip || request.socket?.remoteAddress || 'unknown'
145
193
  request.requestId = requestId
146
194
  }
@@ -204,10 +252,13 @@ function createFastifyLoggingPlugin(options = {}) {
204
252
  requestObj.body = safeSerializeBody(request.body)
205
253
  }
206
254
 
255
+ const otelCtx = getOtelTraceContext()
256
+ const ctx = getRequestContext() || ensureRequestContext()
257
+
207
258
  const meta = {
208
- traceId: reqCtx.traceId,
209
- spanId: reqCtx.spanId,
210
- parentSpanId: reqCtx.parentSpanId || null,
259
+ traceId: otelCtx?.traceId || ctx.traceId,
260
+ spanId: otelCtx?.spanId || ctx.spanId,
261
+ parentSpanId: otelCtx?.parentSpanId || ctx.parentSpanId || null,
211
262
  requestId,
212
263
  request: requestObj,
213
264
  timestamp: Date.now(),
@@ -240,20 +291,53 @@ function createFastifyLoggingPlugin(options = {}) {
240
291
  let requestId, traceId, spanId, parentSpanId, clientIp, query, cachedHeaders
241
292
  let idsCreated = false
242
293
  let headersCached = false
243
- let reqCtx = null
244
294
 
245
- const logWithContext = (level, message, meta) => {
246
- if (!reqCtx) {
247
- const traceHex = sanitizeTraceHex(request.headers['x-trace-id'])
248
- reqCtx = startRequestContext({
249
- requestId: requestId || request.requestId || fastUUID(),
250
- traceHex,
251
- parentSpanId: request.headers['x-parent-span-id'] || null
252
- })
295
+ function getOtelTraceContext() {
296
+ try {
297
+ const activeContext = otelContext.active()
298
+ const span = trace.getSpan(activeContext)
299
+ if (span) {
300
+ const spanContext = span.spanContext()
301
+ if (spanContext && spanContext.traceId && spanContext.spanId) {
302
+ const traceHex = spanContext.traceId.replace(/-/g, '')
303
+ return {
304
+ 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,
305
+ spanId: spanContext.spanId,
306
+ parentSpanId: null
307
+ }
308
+ }
309
+ }
310
+ } catch (_) {}
311
+ return null
312
+ }
313
+
314
+ function ensureRequestContext() {
315
+ if (reqCtx) return reqCtx
316
+
317
+ const otelCtx = getOtelTraceContext()
318
+ const traceHex = otelCtx ? otelCtx.traceId.replace(/-/g, '').substring(0, 32) : sanitizeTraceHex(request.headers['x-trace-id'])
319
+
320
+ reqCtx = startRequestContext({
321
+ requestId: request.requestId || fastUUID(),
322
+ traceHex: traceHex || undefined,
323
+ parentSpanId: otelCtx?.parentSpanId || request.headers['x-parent-span-id'] || null
324
+ })
325
+
326
+ if (otelCtx) {
327
+ reqCtx.traceId = otelCtx.traceId
328
+ reqCtx.spanId = otelCtx.spanId
253
329
  }
254
- meta.traceId = meta.traceId || reqCtx.traceId
255
- meta.spanId = meta.spanId || reqCtx.spanId
256
- meta.parentSpanId = meta.parentSpanId || reqCtx.parentSpanId
330
+
331
+ return reqCtx
332
+ }
333
+
334
+ const logWithContext = (level, message, meta) => {
335
+ const otelCtx = getOtelTraceContext()
336
+ const ctx = getRequestContext() || ensureRequestContext()
337
+
338
+ meta.traceId = otelCtx?.traceId || meta.traceId || ctx.traceId
339
+ meta.spanId = otelCtx?.spanId || meta.spanId || ctx.spanId
340
+ meta.parentSpanId = otelCtx?.parentSpanId || meta.parentSpanId || ctx.parentSpanId
257
341
  sendLog(level, message, meta)
258
342
  }
259
343
 
@@ -263,20 +347,20 @@ function createFastifyLoggingPlugin(options = {}) {
263
347
 
264
348
  query = request.query || null
265
349
 
266
- if (!reqCtx) {
267
- const traceHex = sanitizeTraceHex(request.headers['x-trace-id'])
268
- reqCtx = startRequestContext({
269
- requestId: request.requestId || fastUUID(),
270
- traceHex,
271
- parentSpanId: request.headers['x-parent-span-id'] || null
272
- })
273
- }
350
+ const ctx = ensureRequestContext()
274
351
 
275
- requestId = reqCtx.requestId || request.requestId || fastUUID()
276
- traceId = reqCtx.traceId
277
- spanId = reqCtx.spanId
278
- parentSpanId = reqCtx.parentSpanId || request.headers['x-parent-span-id'] || null
352
+ const otelCtx = getOtelTraceContext()
353
+ if (otelCtx) {
354
+ traceId = otelCtx.traceId
355
+ spanId = otelCtx.spanId
356
+ parentSpanId = otelCtx.parentSpanId
357
+ } else {
358
+ traceId = ctx.traceId
359
+ spanId = ctx.spanId
360
+ parentSpanId = ctx.parentSpanId || request.headers['x-parent-span-id'] || null
361
+ }
279
362
 
363
+ requestId = ctx.requestId || request.requestId || fastUUID()
280
364
  clientIp = request.ip || request.socket?.remoteAddress || 'unknown'
281
365
  }
282
366
 
@@ -322,10 +406,13 @@ function createFastifyLoggingPlugin(options = {}) {
322
406
  if (query) requestObj.query = query
323
407
  if (config.captureHeaders && cachedHeaders) requestObj.headers = cachedHeaders
324
408
 
409
+ const otelCtx = getOtelTraceContext()
410
+ const ctx = getRequestContext() || ensureRequestContext()
411
+
325
412
  const meta = {
326
- traceId: reqCtx.traceId,
327
- spanId: reqCtx.spanId,
328
- parentSpanId: reqCtx.parentSpanId || null,
413
+ traceId: otelCtx?.traceId || ctx.traceId,
414
+ spanId: otelCtx?.spanId || ctx.spanId,
415
+ parentSpanId: otelCtx?.parentSpanId || ctx.parentSpanId || null,
329
416
  requestId,
330
417
  request: requestObj,
331
418
  response,
@@ -1,7 +1,17 @@
1
- const { startRequestContext } = require('./store')
1
+ const { startRequestContext, runWithRequestContext, getRequestContext } = require('./store')
2
2
  const { createHttpLoggerTransport } = require('./streams/httpQueue')
3
3
  const os = require('os')
4
4
 
5
+ let trace, otelContext
6
+ try {
7
+ const otelApi = require('@opentelemetry/api')
8
+ trace = otelApi.trace
9
+ otelContext = otelApi.context
10
+ } catch (_) {
11
+ trace = { getSpan: () => null }
12
+ otelContext = { active: () => ({}) }
13
+ }
14
+
5
15
  function fastUUID() {
6
16
  const timestamp = Date.now().toString(36)
7
17
  const randomPart = Math.random().toString(36).substring(2, 15)
@@ -78,18 +88,54 @@ function createRestifyLoggingMiddleware (options = {}) {
78
88
 
79
89
  return function azifyLoggingMiddleware (req, res, next) {
80
90
  const startTime = Date.now()
81
- const requestId = req.requestId || fastUUID()
82
91
 
83
92
  if (res._azifySetup) {
84
93
  return next()
85
94
  }
86
95
  res._azifySetup = true
87
96
 
88
- const traceHex = sanitizeTraceHex(req.headers['x-trace-id'])
89
- const reqCtx = startRequestContext({
90
- requestId,
91
- traceHex,
92
- parentSpanId: req.headers['x-parent-span-id'] || null
97
+ function getOtelTraceContext() {
98
+ try {
99
+ const activeContext = otelContext.active()
100
+ const span = trace.getSpan(activeContext)
101
+ if (span) {
102
+ const spanContext = span.spanContext()
103
+ if (spanContext && spanContext.traceId && spanContext.spanId) {
104
+ const traceHex = spanContext.traceId.replace(/-/g, '')
105
+ return {
106
+ 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,
107
+ spanId: spanContext.spanId,
108
+ parentSpanId: null
109
+ }
110
+ }
111
+ }
112
+ } catch (_) {}
113
+ return null
114
+ }
115
+
116
+ function ensureRequestContext() {
117
+ const otelCtx = getOtelTraceContext()
118
+ const traceHex = otelCtx ? otelCtx.traceId.replace(/-/g, '').substring(0, 32) : sanitizeTraceHex(req.headers['x-trace-id'])
119
+
120
+ const reqCtx = startRequestContext({
121
+ requestId: req.requestId || fastUUID(),
122
+ traceHex: traceHex || undefined,
123
+ parentSpanId: otelCtx?.parentSpanId || req.headers['x-parent-span-id'] || null
124
+ })
125
+
126
+ if (otelCtx) {
127
+ reqCtx.traceId = otelCtx.traceId
128
+ reqCtx.spanId = otelCtx.spanId
129
+ }
130
+
131
+ return reqCtx
132
+ }
133
+
134
+ const reqCtx = ensureRequestContext()
135
+ const requestId = reqCtx.requestId
136
+
137
+ runWithRequestContext(reqCtx, () => {
138
+ next()
93
139
  })
94
140
 
95
141
  let normalizedPath = req.url
@@ -143,11 +189,14 @@ function createRestifyLoggingMiddleware (options = {}) {
143
189
  const duration = Date.now() - startTime
144
190
  const statusCode = responseStatus || res.statusCode || 200
145
191
  const responseHeaders = typeof res.getHeaders === 'function' ? res.getHeaders() : {}
192
+
193
+ const otelCtx = getOtelTraceContext()
194
+ const ctx = getRequestContext() || reqCtx
146
195
 
147
196
  const meta = {
148
- traceId: reqCtx.traceId,
149
- spanId: reqCtx.spanId,
150
- parentSpanId: reqCtx.parentSpanId || null,
197
+ traceId: otelCtx?.traceId || ctx.traceId,
198
+ spanId: otelCtx?.spanId || ctx.spanId,
199
+ parentSpanId: otelCtx?.parentSpanId || ctx.parentSpanId || null,
151
200
  requestId,
152
201
  request: requestSnapshot,
153
202
  response: {