azify-logger 1.0.28 → 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.
@@ -34,35 +34,28 @@ function createRedisProducer(config = {}) {
34
34
  let lastConnectionErrorLog = 0
35
35
  let lastEnqueueErrorLog = 0
36
36
  let connectionErrorCount = 0
37
- const ERROR_LOG_INTERVAL = 300000 // 5 minutos entre logs (evitar logs repetidos)
37
+ const ERROR_LOG_INTERVAL = 300000
38
38
 
39
- // Flag global compartilhada entre todas as instâncias para garantir apenas 1 log por processo
40
39
  if (typeof global.__azifyLoggerRedisErrorLogged === 'undefined') {
41
40
  global.__azifyLoggerRedisErrorLogged = false
42
41
  global.__azifyLoggerRedisErrorLastLog = 0
43
42
  }
44
43
 
45
44
  client.on('error', (err) => {
46
- // Log apenas uma vez por processo inteiro (compartilhado entre producer e worker)
47
45
  const now = Date.now()
48
46
  if (!global.__azifyLoggerRedisErrorLogged && now - global.__azifyLoggerRedisErrorLastLog > ERROR_LOG_INTERVAL) {
49
47
  if (err && (err.code === 'ECONNREFUSED' || err.message?.includes('ECONNREFUSED') || err.message?.includes('Redis'))) {
50
48
  global.__azifyLoggerRedisErrorLogged = true
51
49
  global.__azifyLoggerRedisErrorLastLog = now
52
50
  connectionErrorCount++
53
- // Usar process.stderr.write para evitar interceptação do console
54
- // Mensagem clara: aplicação continua funcionando, apenas logging está desabilitado
55
51
  process.stderr.write('[azify-logger] ⚠️ Redis indisponível. O sistema de logging está desabilitado. A aplicação continua funcionando normalmente.\n')
56
52
  lastConnectionErrorLog = now
57
53
  }
58
54
  }
59
- // Após primeira mensagem, não logar mais - silenciar completamente
60
55
  })
61
56
  client.on('end', () => {
62
- // Não logar - silenciar completamente
63
57
  })
64
58
  client.on('connect', () => {
65
- // Resetar contador quando conectar com sucesso (sem logar)
66
59
  if (connectionErrorCount > 0 || global.__azifyLoggerRedisErrorLogged) {
67
60
  connectionErrorCount = 0
68
61
  lastConnectionErrorLog = 0
@@ -71,52 +64,52 @@ function createRedisProducer(config = {}) {
71
64
  }
72
65
  })
73
66
 
74
- // BATCHING: acumular logs e enviar em batch para reduzir overhead
75
67
  const batch = []
76
68
  let batchTimer = null
77
- const BATCH_SIZE = 100 // Batch size balanceado (200 era muito grande, causava latência)
78
- const BATCH_TIMEOUT = 150 // Timeout balanceado
69
+ const BATCH_SIZE = 1
70
+ const BATCH_TIMEOUT = 0
79
71
  let flushing = false
80
72
 
81
73
  function flushBatch() {
82
74
  if (flushing || batch.length === 0) return
83
75
  flushing = true
84
76
 
85
- // OTIMIZAÇÃO: enviar TODOS os logs disponíveis no batch (até BATCH_SIZE)
86
- // Isso maximiza eficiência do pipeline Redis
87
77
  const entriesToFlush = batch.splice(0, BATCH_SIZE)
88
- if (entriesToFlush.length === 0) {
78
+ if (!entriesToFlush.length) {
89
79
  flushing = false
90
80
  return
91
81
  }
92
82
 
93
83
  setImmediate(() => {
94
- try {
95
- // Usar pipeline do Redis para enviar múltiplos logs de uma vez
84
+ if (entriesToFlush.length === 1) {
85
+ const entry = entriesToFlush[0]
86
+ if (entry) {
87
+ const payload = JSON.stringify(entry)
88
+ client.xadd(streamKey, 'MAXLEN', '~', maxLen, '*', 'entry', payload).catch(() => {})
89
+ }
90
+ } else {
96
91
  const pipeline = client.pipeline()
92
+ let validCount = 0
97
93
 
98
- for (const entry of entriesToFlush) {
99
- try {
100
- const payload = JSON.stringify(entry)
101
- pipeline.xadd(streamKey, 'MAXLEN', '~', maxLen, '*', 'entry', payload)
102
- } catch (err) {
103
- // Erro ao serializar - ignorar silenciosamente
104
- }
94
+ for (let i = 0; i < entriesToFlush.length; i++) {
95
+ const entry = entriesToFlush[i]
96
+ if (!entry) continue
97
+ const payload = JSON.stringify(entry)
98
+ pipeline.xadd(streamKey, 'MAXLEN', '~', maxLen, '*', 'entry', payload)
99
+ validCount++
105
100
  }
106
101
 
107
- // Executar pipeline - muito mais eficiente que múltiplos xadd
108
- pipeline.exec().catch(() => {
109
- // Erro silencioso - não travar aplicação
110
- })
111
- } catch (err) {
112
- // Erro silencioso - não travar aplicação
113
- } finally {
114
- flushing = false
115
- // Se ainda há logs no batch, agendar próximo flush
116
- if (batch.length > 0) {
117
- scheduleFlush()
102
+ if (validCount > 0) {
103
+ pipeline.exec().catch(() => {})
118
104
  }
119
105
  }
106
+
107
+ flushing = false
108
+ if (batch.length >= BATCH_SIZE) {
109
+ flushBatch()
110
+ } else if (batch.length > 0) {
111
+ scheduleFlush()
112
+ }
120
113
  })
121
114
  }
122
115
 
@@ -124,10 +117,14 @@ function createRedisProducer(config = {}) {
124
117
  if (batchTimer || flushing) return
125
118
 
126
119
  if (batch.length >= BATCH_SIZE) {
127
- // Flush imediato se batch está cheio
128
120
  flushBatch()
121
+ } else if (BATCH_TIMEOUT === 0) {
122
+ setImmediate(() => {
123
+ if (!flushing) {
124
+ flushBatch()
125
+ }
126
+ })
129
127
  } else {
130
- // Flush após timeout
131
128
  batchTimer = setTimeout(() => {
132
129
  batchTimer = null
133
130
  flushBatch()
@@ -140,25 +137,26 @@ function createRedisProducer(config = {}) {
140
137
  }
141
138
 
142
139
  function enqueue(entry) {
143
- // OTIMIZAÇÃO: adicionar ao batch ao invés de enviar imediatamente
144
- // Isso reduz drasticamente o número de operações Redis
145
- try {
146
- batch.push(entry)
140
+ batch.push(entry)
141
+
142
+ if (batch.length >= BATCH_SIZE) {
143
+ if (batchTimer) {
144
+ clearTimeout(batchTimer)
145
+ batchTimer = null
146
+ }
147
+ flushBatch()
148
+ } else if (batch.length === 1) {
147
149
  scheduleFlush()
148
- } catch (err) {
149
- // Erro silencioso - não travar aplicação
150
150
  }
151
151
  }
152
152
 
153
153
  async function close() {
154
- // Flush batch restante antes de fechar
155
154
  if (batchTimer) {
156
155
  clearTimeout(batchTimer)
157
156
  batchTimer = null
158
157
  }
159
158
  while (batch.length > 0) {
160
159
  flushBatch()
161
- // Aguardar um pouco para flush completar
162
160
  await new Promise(resolve => setTimeout(resolve, 10))
163
161
  }
164
162
  await client.quit().catch(() => {})
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/sampling.js CHANGED
@@ -36,7 +36,7 @@ const HTTP_CLIENT_MODE = (process.env.AZIFY_LOGGER_HTTP_CLIENT_LOGGING || 'all')
36
36
 
37
37
  const httpClientSampleRate = resolveSampleRate(
38
38
  'AZIFY_LOGGER_HTTP_SAMPLE_RATE',
39
- HTTP_CLIENT_MODE === 'all' ? 1 : 1 // Sempre 1.0 (100%) quando HTTP_CLIENT_MODE === 'all'
39
+ HTTP_CLIENT_MODE === 'all' ? 1 : 1
40
40
  )
41
41
 
42
42
  const sourceSampleRates = {