azify-logger 1.0.26 → 1.0.28

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/server.js CHANGED
@@ -22,6 +22,11 @@ try {
22
22
 
23
23
  const app = express()
24
24
 
25
+ const allowedExternalIPs = (process.env.ALLOWED_SOURCE_IPS || '')
26
+ .split(',')
27
+ .map(ip => ip.trim())
28
+ .filter(Boolean)
29
+
25
30
  app.set('trust proxy', 1)
26
31
 
27
32
  app.use(express.json({ limit: '10mb' }))
@@ -77,7 +82,11 @@ function validateNetworkAccess(req, res, next) {
77
82
  req.connection.remoteAddress ||
78
83
  req.socket.remoteAddress
79
84
 
80
- if (!IS_LOCAL && !isPrivateOrLocalhost(clientIP)) {
85
+ if (
86
+ !IS_LOCAL &&
87
+ !isPrivateOrLocalhost(clientIP) &&
88
+ !(allowedExternalIPs.length > 0 && allowedExternalIPs.includes(clientIP))
89
+ ) {
81
90
  return res.status(403).json({
82
91
  success: false,
83
92
  message: 'Forbidden. Access denied - endpoint only accessible from same server (localhost/private IPs).',
@@ -162,21 +171,40 @@ function escapeForSQLite(str) {
162
171
  return str.replace(/'/g, "''").replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/\r/g, '\\r')
163
172
  }
164
173
 
174
+ function runSqlite(sql) {
175
+ const dbPath = process.env.GRAFANA_SQLITE_PATH || '/var/lib/grafana/grafana.db'
176
+ const grafanaContainer = process.env.GRAFANA_DOCKER_CONTAINER || 'azify-grafana'
177
+ const { execSync } = require('child_process')
178
+ const localDbExists = fs.existsSync(dbPath)
179
+ const escapedForLocal = sql.replace(/"/g, '""')
180
+ const escapedForDocker = sql.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$')
181
+
182
+ const command = localDbExists
183
+ ? `sqlite3 ${dbPath} "${escapedForLocal}"`
184
+ : `docker exec ${grafanaContainer} sh -c "sqlite3 ${dbPath} \\"${escapedForDocker}\\""`;
185
+
186
+ try {
187
+ if (!localDbExists) {
188
+ console.log(`[runSqlite] Executando comando via docker exec no container ${grafanaContainer}`)
189
+ }
190
+ return execSync(command, { encoding: 'utf8' }).trim()
191
+ } catch (error) {
192
+ const stderr = error.stderr?.toString()?.trim()
193
+ throw new Error(stderr || error.message)
194
+ }
195
+ }
196
+
165
197
  async function createOrgViaSQLite(appName) {
166
198
  const dbPath = '/var/lib/grafana/grafana.db'
167
199
 
168
200
  try {
169
201
  console.log(`[createOrgViaSQLite] Verificando database em ${dbPath}...`)
170
202
  if (!fs.existsSync(dbPath)) {
171
- console.error(`[createOrgViaSQLite] Database não encontrado em ${dbPath}`)
172
- throw new Error(`Database não encontrado em ${dbPath}`)
203
+ console.log(`[createOrgViaSQLite] Banco não acessível localmente; tentando via docker exec`)
173
204
  }
174
-
175
- const { execSync } = require('child_process')
176
-
205
+
177
206
  console.log(`[createOrgViaSQLite] Verificando se Organization '${appName}' já existe...`)
178
- const checkCmd = `sqlite3 ${dbPath} "SELECT id FROM org WHERE name='${appName}';" 2>/dev/null || echo ""`
179
- const existingId = execSync(checkCmd, { encoding: 'utf8' }).trim()
207
+ const existingId = runSqlite(`SELECT id FROM org WHERE name='${appName}';`)
180
208
 
181
209
  if (existingId && existingId !== '') {
182
210
  console.log(`[createOrgViaSQLite] ✅ Organization '${appName}' já existe (ID: ${existingId})`)
@@ -184,8 +212,7 @@ async function createOrgViaSQLite(appName) {
184
212
  }
185
213
 
186
214
  console.log(`[createOrgViaSQLite] Criando Organization '${appName}'...`)
187
- const createCmd = `sqlite3 ${dbPath} "INSERT INTO org (name, version, created, updated) VALUES ('${appName}', 1, datetime('now'), datetime('now')); SELECT id FROM org WHERE name='${appName}';" 2>/dev/null`
188
- const newId = execSync(createCmd, { encoding: 'utf8' }).trim()
215
+ const newId = runSqlite(`INSERT INTO org (name, version, created, updated) VALUES ('${appName}', 1, datetime('now'), datetime('now')); SELECT id FROM org WHERE name='${appName}';`)
189
216
 
190
217
  if (newId && newId !== '') {
191
218
  console.log(`[createOrgViaSQLite] ✅ Organization '${appName}' criada com sucesso (ID: ${newId})`)
@@ -201,10 +228,14 @@ async function createOrgViaSQLite(appName) {
201
228
  }
202
229
 
203
230
  async function setupGrafanaForApp(appName) {
204
- const grafanaUrl = process.env.GRAFANA_URL || 'http://azify-grafana:3000'
231
+ const runningInDocker = fs.existsSync('/.dockerenv')
232
+ const defaultGrafanaUrl = runningInDocker ? 'http://azify-grafana:3000' : 'http://127.0.0.1:3002'
233
+ const grafanaUrl = process.env.GRAFANA_URL || defaultGrafanaUrl
205
234
  const grafanaAdminUser = process.env.GRAFANA_ADMIN_USER || 'admin'
206
235
  const grafanaAdminPassword = process.env.GRAFANA_ADMIN_PASSWORD || process.env.GF_SECURITY_ADMIN_PASSWORD || 'admin'
207
- const opensearchUrl = process.env.OPENSEARCH_URL || 'http://azify-opensearch:9200'
236
+ const defaultOpensearchUrl = runningInDocker ? 'http://azify-opensearch:9200' : 'http://127.0.0.1:9200'
237
+ const opensearchUrl = process.env.OPENSEARCH_URL || defaultOpensearchUrl
238
+ const grafanaOpensearchUrl = process.env.GRAFANA_OPENSEARCH_URL || (runningInDocker ? 'http://azify-opensearch:9200' : opensearchUrl)
208
239
 
209
240
  if (!setupGrafanaForApp._cache) {
210
241
  setupGrafanaForApp._cache = new Set()
@@ -252,11 +283,11 @@ async function setupGrafanaForApp(appName) {
252
283
  org = await createOrgViaSQLite(appName)
253
284
  if (!org) {
254
285
  console.error(`[setupGrafana] ❌ Não foi possível criar Organization via SQLite para ${appName}`)
255
- return
286
+ throw new Error(`SQLite não criou organization para ${appName}`)
256
287
  }
257
288
  } catch (sqliteError) {
258
289
  console.error(`[setupGrafana] ❌ Erro ao criar Organization via SQLite: ${sqliteError.message}`)
259
- return
290
+ throw sqliteError
260
291
  }
261
292
  }
262
293
  } else {
@@ -266,18 +297,18 @@ async function setupGrafanaForApp(appName) {
266
297
  org = await createOrgViaSQLite(appName)
267
298
  if (!org) {
268
299
  console.error(`[setupGrafana] ❌ Não foi possível criar Organization via SQLite para ${appName}`)
269
- return
300
+ throw new Error(`SQLite não criou organization para ${appName}`)
270
301
  }
271
302
  } catch (sqliteError) {
272
303
  console.error(`[setupGrafana] ❌ Erro ao criar Organization via SQLite: ${sqliteError.message}`)
273
- return
304
+ throw sqliteError
274
305
  }
275
306
  }
276
307
  }
277
308
 
278
309
  if (!org) {
279
310
  console.error(`[setupGrafana] ❌ Organization não encontrada/criada para ${appName}`)
280
- return
311
+ throw new Error(`Organization não encontrada ou criada para ${appName}`)
281
312
  }
282
313
 
283
314
  console.log(`[setupGrafana] ✅ Organization encontrada/criada: ${org.name} (ID: ${org.id})`)
@@ -330,20 +361,21 @@ async function setupGrafanaForApp(appName) {
330
361
  }
331
362
  } catch (userError) {
332
363
  const dbUserId = await new Promise((resolve) => {
333
- const { execSync } = require('child_process')
334
364
  try {
335
- const result = execSync(`sqlite3 /var/lib/grafana/grafana.db "SELECT id FROM user WHERE email='${trimmedEmail}';"`, { encoding: 'utf8' }).trim()
365
+ const result = runSqlite(`SELECT id FROM user WHERE email='${trimmedEmail}';`)
336
366
  resolve(result || null)
337
- } catch {
367
+ } catch (err) {
368
+ console.error(`[setupGrafana] ❌ Falha ao buscar usuário ${trimmedEmail} via SQLite: ${err.message}`)
338
369
  resolve(null)
339
370
  }
340
371
  })
341
372
 
342
373
  if (dbUserId) {
343
- const { execSync } = require('child_process')
344
374
  try {
345
- execSync(`sqlite3 /var/lib/grafana/grafana.db "INSERT OR IGNORE INTO org_user (org_id, user_id, role, created, updated) VALUES (${org.id}, ${dbUserId}, 'Admin', datetime('now'), datetime('now')); UPDATE org_user SET role='Admin' WHERE org_id=${org.id} AND user_id=${dbUserId};"`, { encoding: 'utf8' })
346
- } catch {
375
+ runSqlite(`INSERT OR IGNORE INTO org_user (org_id, user_id, role, created, updated) VALUES (${org.id}, ${dbUserId}, 'Admin', datetime('now'), datetime('now')); UPDATE org_user SET role='Admin' WHERE org_id=${org.id} AND user_id=${dbUserId};`)
376
+ } catch (fallbackError) {
377
+ console.error(`[setupGrafana] ❌ Falha ao promover ${trimmedEmail} via SQLite: ${fallbackError.message}`)
378
+ throw fallbackError
347
379
  }
348
380
  }
349
381
  }
@@ -357,7 +389,7 @@ async function setupGrafanaForApp(appName) {
357
389
  name: `OpenSearch-${appName}`,
358
390
  type: 'grafana-opensearch-datasource',
359
391
  access: 'proxy',
360
- url: opensearchUrl,
392
+ url: grafanaOpensearchUrl,
361
393
  uid: datasourceUid,
362
394
  isDefault: true,
363
395
  jsonData: {
@@ -416,25 +448,16 @@ async function setupGrafanaForApp(appName) {
416
448
  if (createError.response?.status === 401) {
417
449
  console.log(`[setupGrafana] 🔄 Tentando criar datasource via SQLite...`)
418
450
  try {
419
- const { execSync } = require('child_process')
420
451
  const dsJson = JSON.stringify(datasourceConfig.jsonData)
421
452
  const dsName = escapeForSQLite(datasourceConfig.name)
422
- const opensearchUrlEscaped = escapeForSQLite(opensearchUrl)
453
+ const opensearchUrlEscaped = escapeForSQLite(grafanaOpensearchUrl)
423
454
  const dsUidEscaped = escapeForSQLite(datasourceUid)
424
455
  const jsonEscaped = dsJson.replace(/'/g, "''")
425
- const tempFile = `/tmp/ds_${org.id}_${Date.now()}.sql`
426
- const sql = `INSERT INTO data_source (org_id, version, type, name, access, url, is_default, json_data, uid, created, updated, basic_auth, with_credentials) VALUES (${org.id}, 1, 'grafana-opensearch-datasource', '${dsName}', 'proxy', '${opensearchUrlEscaped}', ${datasourceConfig.isDefault ? 1 : 0}, '${jsonEscaped}', '${dsUidEscaped}', datetime('now'), datetime('now'), 0, 0); SELECT last_insert_rowid();`
427
- fs.writeFileSync(tempFile, sql)
428
- const cmd = `sqlite3 /var/lib/grafana/grafana.db < ${tempFile}`
429
- const dsId = execSync(cmd, { encoding: 'utf8' }).trim()
430
- fs.unlinkSync(tempFile)
456
+ runSqlite(`INSERT INTO data_source (org_id, version, type, name, access, url, is_default, json_data, uid, created, updated, basic_auth, with_credentials) VALUES (${org.id}, 1, 'grafana-opensearch-datasource', '${dsName}', 'proxy', '${opensearchUrlEscaped}', ${datasourceConfig.isDefault ? 1 : 0}, '${jsonEscaped}', '${dsUidEscaped}', datetime('now'), datetime('now'), 0, 0);`)
457
+ const dsId = runSqlite('SELECT last_insert_rowid();')
431
458
  console.log(`[setupGrafana] ✅ Datasource criado via SQLite: ${datasourceUid} (ID: ${dsId})`)
432
459
  } catch (sqliteError) {
433
460
  console.error(`[setupGrafana] ❌ Erro ao criar datasource via SQLite: ${sqliteError.message}`)
434
- try {
435
- const tempFiles = fs.readdirSync('/tmp').filter(f => f.startsWith('ds_'))
436
- tempFiles.forEach(f => { try { fs.unlinkSync(`/tmp/${f}`) } catch {} })
437
- } catch {}
438
461
  }
439
462
  }
440
463
  }
@@ -443,25 +466,16 @@ async function setupGrafanaForApp(appName) {
443
466
  if (error.response?.status === 401) {
444
467
  console.log(`[setupGrafana] 🔄 Tentando criar datasource via SQLite (erro 401 na verificação)...`)
445
468
  try {
446
- const { execSync } = require('child_process')
447
469
  const dsJson = JSON.stringify(datasourceConfig.jsonData)
448
470
  const dsName = escapeForSQLite(datasourceConfig.name)
449
- const opensearchUrlEscaped = escapeForSQLite(opensearchUrl)
471
+ const opensearchUrlEscaped = escapeForSQLite(grafanaOpensearchUrl)
450
472
  const dsUidEscaped = escapeForSQLite(datasourceUid)
451
473
  const jsonEscaped = dsJson.replace(/'/g, "''")
452
- const tempFile = `/tmp/ds2_${org.id}_${Date.now()}.sql`
453
- const sql = `INSERT INTO data_source (org_id, version, type, name, access, url, is_default, json_data, uid, created, updated, basic_auth, with_credentials) VALUES (${org.id}, 1, 'grafana-opensearch-datasource', '${dsName}', 'proxy', '${opensearchUrlEscaped}', ${datasourceConfig.isDefault ? 1 : 0}, '${jsonEscaped}', '${dsUidEscaped}', datetime('now'), datetime('now'), 0, 0); SELECT last_insert_rowid();`
454
- fs.writeFileSync(tempFile, sql)
455
- const cmd = `sqlite3 /var/lib/grafana/grafana.db < ${tempFile}`
456
- const dsId = execSync(cmd, { encoding: 'utf8' }).trim()
457
- fs.unlinkSync(tempFile)
474
+ runSqlite(`INSERT INTO data_source (org_id, version, type, name, access, url, is_default, json_data, uid, created, updated, basic_auth, with_credentials) VALUES (${org.id}, 1, 'grafana-opensearch-datasource', '${dsName}', 'proxy', '${opensearchUrlEscaped}', ${datasourceConfig.isDefault ? 1 : 0}, '${jsonEscaped}', '${dsUidEscaped}', datetime('now'), datetime('now'), 0, 0);`)
475
+ const dsId = runSqlite('SELECT last_insert_rowid();')
458
476
  console.log(`[setupGrafana] ✅ Datasource criado via SQLite: ${datasourceUid} (ID: ${dsId})`)
459
477
  } catch (sqliteError) {
460
478
  console.error(`[setupGrafana] ❌ Erro ao criar datasource via SQLite: ${sqliteError.message}`)
461
- try {
462
- const tempFiles = fs.readdirSync('/tmp').filter(f => f.startsWith('ds'))
463
- tempFiles.forEach(f => { try { fs.unlinkSync(`/tmp/${f}`) } catch {} })
464
- } catch {}
465
479
  }
466
480
  }
467
481
  }
@@ -711,7 +725,6 @@ async function setupGrafanaForApp(appName) {
711
725
  if (error.response?.status === 401 && typeof dashboardConfig !== 'undefined' && dashboardConfig && dashboardConfig.dashboard) {
712
726
  console.log(`[setupGrafana] 🔄 Tentando criar dashboard via SQLite...`)
713
727
  try {
714
- const { execSync } = require('child_process')
715
728
  const dashboardUid = `dashboard-${appName.toLowerCase()}`
716
729
  const dashboardTitle = `Application Health - ${appName}`
717
730
  const dashboardJson = JSON.stringify(dashboardConfig.dashboard)
@@ -720,19 +733,11 @@ async function setupGrafanaForApp(appName) {
720
733
  const slug = dashboardTitle.toLowerCase().replace(/[^a-z0-9-]/g, '-')
721
734
  const slugEscaped = escapeForSQLite(slug)
722
735
  const uidEscaped = escapeForSQLite(dashboardUid)
723
- const tempFile = `/tmp/dash_${org.id}_${Date.now()}.sql`
724
- const sql = `INSERT INTO dashboard (org_id, version, slug, title, data, created, updated, uid, is_folder, folder_id) VALUES (${org.id}, 0, '${slugEscaped}', '${escapedTitle}', '${escapedJson}', datetime('now'), datetime('now'), '${uidEscaped}', 0, 0); SELECT last_insert_rowid();`
725
- fs.writeFileSync(tempFile, sql)
726
- const cmd = `sqlite3 /var/lib/grafana/grafana.db < ${tempFile}`
727
- const dashboardId = execSync(cmd, { encoding: 'utf8' }).trim()
728
- fs.unlinkSync(tempFile)
736
+ runSqlite(`INSERT INTO dashboard (org_id, version, slug, title, data, created, updated, uid, is_folder, folder_id) VALUES (${org.id}, 0, '${slugEscaped}', '${escapedTitle}', '${escapedJson}', datetime('now'), datetime('now'), '${uidEscaped}', 0, 0);`)
737
+ const dashboardId = runSqlite('SELECT last_insert_rowid();')
729
738
  console.log(`[setupGrafana] ✅ Dashboard criado via SQLite: ${dashboardTitle} (ID: ${dashboardId})`)
730
739
  } catch (sqliteError) {
731
740
  console.error(`[setupGrafana] ❌ Erro ao criar dashboard via SQLite: ${sqliteError.message}`)
732
- try {
733
- const tempFiles = fs.readdirSync('/tmp').filter(f => f.startsWith('dash_'))
734
- tempFiles.forEach(f => { try { fs.unlinkSync(`/tmp/${f}`) } catch {} })
735
- } catch {}
736
741
  }
737
742
  } else {
738
743
  console.error(`[setupGrafana] ⚠️ Não foi possível criar dashboard via SQLite: dashboardConfig não disponível`)
@@ -932,7 +937,7 @@ async function handleLog(req, res) {
932
937
  version: (meta && meta.service && meta.service.version) || '1.0.0'
933
938
  },
934
939
  appName: (meta && meta.appName) || (meta && meta.service && meta.service.name) || undefined,
935
- environment: (meta && meta.environment) || process.env.NODE_ENV || 'development',
940
+ environment: (meta && meta.environment) || process.env.NODE_ENV,
936
941
  hostname: (meta && meta.hostname) || os.hostname(),
937
942
  traceId: traceContext.traceId,
938
943
  spanId: traceContext.spanId,
@@ -952,22 +957,90 @@ async function handleLog(req, res) {
952
957
  }
953
958
  }
954
959
 
955
- if (key === 'responseBody') {
956
- if (typeof value === 'object' && value !== null) {
960
+ const truncateBody = (bodyValue, forResponse = false) => {
961
+ if (forResponse && typeof bodyValue === 'string') {
962
+ if (bodyValue.length > 10000) {
963
+ return bodyValue.substring(0, 10000) + '... [truncated]'
964
+ }
965
+ return bodyValue
966
+ }
967
+
968
+ if (typeof bodyValue === 'string') {
969
+ if (!forResponse && (bodyValue.trim().startsWith('{') || bodyValue.trim().startsWith('['))) {
970
+ try {
971
+ let parsed = JSON.parse(bodyValue)
972
+ if (typeof parsed === 'object') {
973
+ let serialized = JSON.stringify(parsed)
974
+ if (serialized.length > 10000) {
975
+ return bodyValue.substring(0, 10000) + '... [truncated]'
976
+ }
977
+ return parsed
978
+ }
979
+ } catch (_) { }
980
+ }
981
+ if (bodyValue.length > 10000) {
982
+ return bodyValue.substring(0, 10000) + '... [truncated]'
983
+ }
984
+ return bodyValue
985
+ } else if (typeof bodyValue === 'object' && bodyValue !== null) {
957
986
  try {
958
- value = JSON.stringify(value)
987
+ let serialized = JSON.stringify(bodyValue)
988
+ if (serialized.length > 10000 || forResponse) {
989
+ return serialized.substring(0, 10000) + '... [truncated]'
990
+ }
991
+ return bodyValue
959
992
  } catch (e) {
960
- return
993
+ try {
994
+ let str = String(bodyValue)
995
+ if (str.length > 10000) {
996
+ return str.substring(0, 10000) + '... [truncated]'
997
+ }
998
+ return str
999
+ } catch (_) {
1000
+ return '[Unable to serialize body]'
1001
+ }
1002
+ }
1003
+ } else if (Buffer.isBuffer(bodyValue)) {
1004
+ try {
1005
+ let str = bodyValue.toString('utf8')
1006
+ if (str.length > 10000) {
1007
+ return str.substring(0, 10000) + '... [truncated]'
1008
+ }
1009
+ if (!forResponse && (str.trim().startsWith('{') || str.trim().startsWith('['))) {
1010
+ try {
1011
+ return JSON.parse(str)
1012
+ } catch (_) {
1013
+ return str
1014
+ }
1015
+ }
1016
+ return str
1017
+ } catch (_) {
1018
+ return '[Unable to serialize Buffer]'
961
1019
  }
962
- } else if (typeof value === 'string' && value.length > 10000) {
963
- value = value.substring(0, 10000) + '... [truncated]'
964
- }
965
- if (typeof value === 'object' && value !== null) {
966
- return
967
1020
  }
1021
+ return bodyValue
968
1022
  }
969
1023
 
970
- logEntry[key] = value
1024
+ if (key === 'request' && value && typeof value === 'object' && value.body !== undefined) {
1025
+ const processedBody = truncateBody(value.body, false)
1026
+ if (typeof processedBody === 'string' && processedBody.length > 5000) {
1027
+ logEntry.requestBody = processedBody
1028
+ delete value.body
1029
+ } else {
1030
+ value.body = processedBody
1031
+ }
1032
+ } else if (key === 'response' && value && typeof value === 'object' && value.body !== undefined) {
1033
+ const processedBody = truncateBody(value.body, true)
1034
+ const bodyString = typeof processedBody === 'string' ? processedBody : JSON.stringify(processedBody)
1035
+ logEntry.responseBody = bodyString
1036
+ const { body, ...responseWithoutBody } = value
1037
+ logEntry[key] = responseWithoutBody
1038
+ } else if (key === 'responseBody' || key === 'requestBody') {
1039
+ value = truncateBody(value)
1040
+ logEntry[key] = value
1041
+ } else {
1042
+ logEntry[key] = value
1043
+ }
971
1044
  }
972
1045
  })
973
1046
  }
@@ -980,6 +1053,10 @@ async function handleLog(req, res) {
980
1053
  const serviceName = appName.toLowerCase().replace(/[^a-z0-9-]/g, '-')
981
1054
  const indexName = `logs-${serviceName}`
982
1055
 
1056
+ if (logEntry.responseBody && typeof logEntry.responseBody === 'string' && logEntry.responseBody.length > 10000) {
1057
+ logEntry.responseBody = logEntry.responseBody.substring(0, 10000) + '... [truncated]'
1058
+ }
1059
+
983
1060
  await axios.post(`${osUrl}/${indexName}/_doc`, logEntry, {
984
1061
  headers: { 'Content-Type': 'application/json' }
985
1062
  })
@@ -1004,6 +1081,27 @@ async function handleLog(req, res) {
1004
1081
 
1005
1082
  res.json({ success: true, message: 'Log enviado com sucesso', index: indexName })
1006
1083
  } catch (error) {
1084
+ const status = error?.response?.status
1085
+ const errorMsg = error?.response?.data?.error?.reason ||
1086
+ error?.response?.data?.message ||
1087
+ error?.message ||
1088
+ 'Erro desconhecido'
1089
+
1090
+ console.error('❌ Falha ao enviar log para OpenSearch', {
1091
+ status,
1092
+ message: errorMsg
1093
+ })
1094
+
1095
+ if (error?.response?.data) {
1096
+ try {
1097
+ console.error('📦 Resposta OpenSearch:', JSON.stringify(error.response.data).substring(0, 2000))
1098
+ } catch (_) {
1099
+ console.error('📦 Resposta OpenSearch (raw):', error.response.data)
1100
+ }
1101
+ } else if (error?.code) {
1102
+ console.error('⚙️ Código de erro:', error.code)
1103
+ }
1104
+
1007
1105
  res.status(500).json({ success: false, message: 'Erro ao enviar log para OpenSearch' })
1008
1106
  }
1009
1107
  }
@@ -1,9 +1,35 @@
1
1
  import { Writable } from 'stream';
2
2
 
3
+ export interface HttpTransportOptions {
4
+ timeout?: number;
5
+ maxQueueSize?: number;
6
+ batchSize?: number;
7
+ flushInterval?: number;
8
+ maxSockets?: number;
9
+ failureThreshold?: number;
10
+ failureCooldown?: number;
11
+ redisUrl?: string;
12
+ redisQueueKey?: string;
13
+ redisMaxQueueLen?: number;
14
+ redisWorker?: boolean;
15
+ }
16
+
3
17
  export interface BunyanStreamOptions {
4
18
  loggerUrl?: string;
5
19
  serviceName?: string;
6
20
  environment?: string;
21
+ transportOptions?: HttpTransportOptions;
22
+ timeout?: number;
23
+ maxQueueSize?: number;
24
+ batchSize?: number;
25
+ flushInterval?: number;
26
+ maxSockets?: number;
27
+ failureThreshold?: number;
28
+ failureCooldown?: number;
29
+ redisUrl?: string;
30
+ redisQueueKey?: string;
31
+ redisMaxQueueLen?: number;
32
+ redisWorker?: boolean;
7
33
  }
8
34
 
9
35
  /**
package/streams/bunyan.js CHANGED
@@ -1,12 +1,12 @@
1
- const { AsyncLocalStorage } = require('async_hooks')
2
- const axios = require('axios')
3
-
4
- const globalContext = new Map()
1
+ const { createHttpLoggerTransport } = require('./httpQueue')
2
+ const { shouldSample, markSource } = require('../sampling')
5
3
 
6
4
  function createBunyanStream(options = {}) {
7
- const loggerUrl = options.loggerUrl || process.env.AZIFY_LOGGER_URL || 'http://localhost:3001'
8
- const serviceName = options.serviceName || process.env.APP_NAME || 'app'
9
- const environment = options.environment || process.env.NODE_ENV || 'development'
5
+ const loggerUrl = options.loggerUrl || process.env.AZIFY_LOGGER_URL
6
+ const serviceName = options.serviceName || process.env.APP_NAME
7
+ const environment = options.environment || process.env.NODE_ENV
8
+
9
+ const transport = createHttpLoggerTransport(loggerUrl, extractTransportOptions(options))
10
10
 
11
11
  return {
12
12
  write(record) {
@@ -52,11 +52,42 @@ function createBunyanStream(options = {}) {
52
52
  ...(spanId && { spanId })
53
53
  }
54
54
 
55
+ markSource(meta, 'logger')
56
+
57
+ if (!shouldSample(level, 'logger')) {
58
+ return
59
+ }
60
+
55
61
  const payload = { level, message: record.msg || record.message || 'log', meta }
56
62
 
57
- axios.post(`${loggerUrl}`, payload, { headers, timeout: 10000 }).catch(() => {})
63
+ transport.enqueue(payload, headers)
58
64
  }
59
65
  }
60
66
  }
61
67
 
62
68
  module.exports = createBunyanStream
69
+
70
+ function extractTransportOptions (options) {
71
+ const {
72
+ transportOptions = {},
73
+ timeout,
74
+ maxQueueSize,
75
+ batchSize,
76
+ flushInterval,
77
+ maxSockets,
78
+ failureThreshold,
79
+ failureCooldown
80
+ } = options
81
+
82
+ const overrides = { ...transportOptions }
83
+
84
+ if (timeout != null) overrides.timeout = timeout
85
+ if (maxQueueSize != null) overrides.maxQueueSize = maxQueueSize
86
+ if (batchSize != null) overrides.batchSize = batchSize
87
+ if (flushInterval != null) overrides.flushInterval = flushInterval
88
+ if (maxSockets != null) overrides.maxSockets = maxSockets
89
+ if (failureThreshold != null) overrides.failureThreshold = failureThreshold
90
+ if (failureCooldown != null) overrides.failureCooldown = failureCooldown
91
+
92
+ return overrides
93
+ }