azify-logger 1.0.26 → 1.0.29
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 +26 -5
- package/index.js +40 -17
- package/middleware-express.js +267 -366
- package/middleware-fastify.js +348 -0
- package/middleware-restify.js +147 -303
- package/package.json +31 -30
- package/queue/fileQueue.js +100 -0
- package/queue/redisQueue.js +179 -0
- package/queue/workerManager.js +111 -0
- package/register-otel.js +63 -13
- package/register.js +364 -99
- package/sampling.js +79 -0
- package/scripts/redis-worker.js +439 -0
- package/server.js +169 -74
- package/store.js +10 -4
- package/streams/bunyan.d.ts +26 -0
- package/streams/bunyan.js +39 -8
- package/streams/httpQueue.js +342 -0
- package/streams/pino.d.ts +38 -0
- package/streams/pino.js +44 -7
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 (
|
|
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).',
|
|
@@ -111,7 +120,7 @@ async function ensureIndexTemplate() {
|
|
|
111
120
|
mappings: {
|
|
112
121
|
properties: {
|
|
113
122
|
'@timestamp': { type: 'date' },
|
|
114
|
-
level: { type: 'keyword' },
|
|
123
|
+
level: { type: 'text', fields: { keyword: { type: 'keyword' } } },
|
|
115
124
|
message: { type: 'text' },
|
|
116
125
|
service: {
|
|
117
126
|
properties: {
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
392
|
+
url: grafanaOpensearchUrl,
|
|
361
393
|
uid: datasourceUid,
|
|
362
394
|
isDefault: true,
|
|
363
395
|
jsonData: {
|
|
@@ -367,7 +399,7 @@ async function setupGrafanaForApp(appName) {
|
|
|
367
399
|
esVersion: '2.11.1',
|
|
368
400
|
version: '2.11.1',
|
|
369
401
|
logMessageField: 'message',
|
|
370
|
-
logLevelField: 'level',
|
|
402
|
+
logLevelField: 'level.keyword',
|
|
371
403
|
maxConcurrentShardRequests: 5,
|
|
372
404
|
includeFrozen: false,
|
|
373
405
|
xpack: false,
|
|
@@ -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(
|
|
453
|
+
const opensearchUrlEscaped = escapeForSQLite(grafanaOpensearchUrl)
|
|
423
454
|
const dsUidEscaped = escapeForSQLite(datasourceUid)
|
|
424
455
|
const jsonEscaped = dsJson.replace(/'/g, "''")
|
|
425
|
-
|
|
426
|
-
const
|
|
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(
|
|
471
|
+
const opensearchUrlEscaped = escapeForSQLite(grafanaOpensearchUrl)
|
|
450
472
|
const dsUidEscaped = escapeForSQLite(datasourceUid)
|
|
451
473
|
const jsonEscaped = dsJson.replace(/'/g, "''")
|
|
452
|
-
|
|
453
|
-
const
|
|
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
|
}
|
|
@@ -491,7 +505,7 @@ async function setupGrafanaForApp(appName) {
|
|
|
491
505
|
datasource: { uid: datasourceUid, type: 'grafana-opensearch-datasource' },
|
|
492
506
|
targets: [{
|
|
493
507
|
refId: 'A',
|
|
494
|
-
query: `message:"[
|
|
508
|
+
query: `message:"[RESPONSE]" AND ${indexFilter}`,
|
|
495
509
|
bucketAggs: [{
|
|
496
510
|
id: '2',
|
|
497
511
|
type: 'date_histogram',
|
|
@@ -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
|
-
|
|
724
|
-
const
|
|
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
|
|
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,91 @@ async function handleLog(req, res) {
|
|
|
952
957
|
}
|
|
953
958
|
}
|
|
954
959
|
|
|
955
|
-
|
|
956
|
-
if (
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
960
|
-
return
|
|
960
|
+
const truncateBody = (bodyValue, forResponse = false) => {
|
|
961
|
+
if (forResponse) {
|
|
962
|
+
if (typeof bodyValue === 'string') {
|
|
963
|
+
return bodyValue
|
|
964
|
+
} else if (Buffer.isBuffer(bodyValue)) {
|
|
965
|
+
return bodyValue.toString('utf8')
|
|
966
|
+
} else if (typeof bodyValue === 'object' && bodyValue !== null) {
|
|
967
|
+
try {
|
|
968
|
+
return JSON.stringify(bodyValue)
|
|
969
|
+
} catch (_) {
|
|
970
|
+
return String(bodyValue)
|
|
971
|
+
}
|
|
961
972
|
}
|
|
962
|
-
|
|
963
|
-
value = value.substring(0, 10000) + '... [truncated]'
|
|
973
|
+
return String(bodyValue)
|
|
964
974
|
}
|
|
965
|
-
|
|
966
|
-
|
|
975
|
+
|
|
976
|
+
if (typeof bodyValue === 'string') {
|
|
977
|
+
if (bodyValue.trim().startsWith('{') || bodyValue.trim().startsWith('[')) {
|
|
978
|
+
try {
|
|
979
|
+
let parsed = JSON.parse(bodyValue)
|
|
980
|
+
if (typeof parsed === 'object') {
|
|
981
|
+
return parsed
|
|
982
|
+
}
|
|
983
|
+
} catch (_) { }
|
|
984
|
+
}
|
|
985
|
+
return bodyValue
|
|
986
|
+
} else if (typeof bodyValue === 'object' && bodyValue !== null) {
|
|
987
|
+
return bodyValue
|
|
988
|
+
} else if (Buffer.isBuffer(bodyValue)) {
|
|
989
|
+
try {
|
|
990
|
+
let str = bodyValue.toString('utf8')
|
|
991
|
+
if (str.trim().startsWith('{') || str.trim().startsWith('[')) {
|
|
992
|
+
try {
|
|
993
|
+
return JSON.parse(str)
|
|
994
|
+
} catch (_) {
|
|
995
|
+
return str
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return str
|
|
999
|
+
} catch (_) {
|
|
1000
|
+
return '[Unable to serialize Buffer]'
|
|
1001
|
+
}
|
|
967
1002
|
}
|
|
1003
|
+
return bodyValue
|
|
968
1004
|
}
|
|
969
1005
|
|
|
970
|
-
|
|
1006
|
+
if (key === 'request' && value && typeof value === 'object' && value.body !== undefined) {
|
|
1007
|
+
const processedBody = truncateBody(value.body, false)
|
|
1008
|
+
if (typeof processedBody === 'string' && processedBody.length > 5000) {
|
|
1009
|
+
logEntry.requestBody = processedBody
|
|
1010
|
+
delete value.body
|
|
1011
|
+
} else {
|
|
1012
|
+
value.body = processedBody
|
|
1013
|
+
}
|
|
1014
|
+
} else if (key === 'response' && value && typeof value === 'object') {
|
|
1015
|
+
if (value.statusCode != null) {
|
|
1016
|
+
logEntry.statusCode = value.statusCode
|
|
1017
|
+
}
|
|
1018
|
+
if (value.durationMs != null) {
|
|
1019
|
+
logEntry.responseTime = value.durationMs
|
|
1020
|
+
}
|
|
1021
|
+
if (value.body !== undefined) {
|
|
1022
|
+
const processedBody = truncateBody(value.body, true)
|
|
1023
|
+
let bodyString
|
|
1024
|
+
if (typeof processedBody === 'string') {
|
|
1025
|
+
bodyString = processedBody
|
|
1026
|
+
} else {
|
|
1027
|
+
bodyString = JSON.stringify(processedBody)
|
|
1028
|
+
}
|
|
1029
|
+
logEntry.responseBody = bodyString
|
|
1030
|
+
const { body, ...responseWithoutBody } = value
|
|
1031
|
+
logEntry[key] = responseWithoutBody
|
|
1032
|
+
} else {
|
|
1033
|
+
logEntry[key] = value
|
|
1034
|
+
}
|
|
1035
|
+
} else if (key === 'responseBody' || key === 'requestBody') {
|
|
1036
|
+
if (key === 'responseBody') {
|
|
1037
|
+
value = truncateBody(value, true)
|
|
1038
|
+
} else {
|
|
1039
|
+
value = truncateBody(value, false)
|
|
1040
|
+
}
|
|
1041
|
+
logEntry[key] = value
|
|
1042
|
+
} else {
|
|
1043
|
+
logEntry[key] = value
|
|
1044
|
+
}
|
|
971
1045
|
}
|
|
972
1046
|
})
|
|
973
1047
|
}
|
|
@@ -1004,6 +1078,27 @@ async function handleLog(req, res) {
|
|
|
1004
1078
|
|
|
1005
1079
|
res.json({ success: true, message: 'Log enviado com sucesso', index: indexName })
|
|
1006
1080
|
} catch (error) {
|
|
1081
|
+
const status = error?.response?.status
|
|
1082
|
+
const errorMsg = error?.response?.data?.error?.reason ||
|
|
1083
|
+
error?.response?.data?.message ||
|
|
1084
|
+
error?.message ||
|
|
1085
|
+
'Erro desconhecido'
|
|
1086
|
+
|
|
1087
|
+
console.error('❌ Falha ao enviar log para OpenSearch', {
|
|
1088
|
+
status,
|
|
1089
|
+
message: errorMsg
|
|
1090
|
+
})
|
|
1091
|
+
|
|
1092
|
+
if (error?.response?.data) {
|
|
1093
|
+
try {
|
|
1094
|
+
console.error('📦 Resposta OpenSearch:', JSON.stringify(error.response.data).substring(0, 2000))
|
|
1095
|
+
} catch (_) {
|
|
1096
|
+
console.error('📦 Resposta OpenSearch (raw):', error.response.data)
|
|
1097
|
+
}
|
|
1098
|
+
} else if (error?.code) {
|
|
1099
|
+
console.error('⚙️ Código de erro:', error.code)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1007
1102
|
res.status(500).json({ success: false, message: 'Erro ao enviar log para OpenSearch' })
|
|
1008
1103
|
}
|
|
1009
1104
|
}
|
package/store.js
CHANGED
|
@@ -2,8 +2,14 @@ const { AsyncLocalStorage } = require('async_hooks')
|
|
|
2
2
|
|
|
3
3
|
const als = new AsyncLocalStorage()
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
|
|
5
|
+
function fastGenerateId(length = 16) {
|
|
6
|
+
const chars = '0123456789abcdef'
|
|
7
|
+
let result = ''
|
|
8
|
+
const totalChars = length * 2
|
|
9
|
+
for (let i = 0; i < totalChars; i++) {
|
|
10
|
+
result += chars[Math.floor(Math.random() * chars.length)]
|
|
11
|
+
}
|
|
12
|
+
return result
|
|
7
13
|
}
|
|
8
14
|
|
|
9
15
|
function toTraceId(hex32) {
|
|
@@ -12,8 +18,8 @@ function toTraceId(hex32) {
|
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
function startRequestContext(initial = {}) {
|
|
15
|
-
const traceHex = initial.traceHex ||
|
|
16
|
-
const spanHex = initial.spanHex ||
|
|
21
|
+
const traceHex = initial.traceHex || fastGenerateId(16)
|
|
22
|
+
const spanHex = initial.spanHex || fastGenerateId(8)
|
|
17
23
|
const ctx = {
|
|
18
24
|
traceId: toTraceId(traceHex),
|
|
19
25
|
spanId: spanHex,
|
package/streams/bunyan.d.ts
CHANGED
|
@@ -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 {
|
|
2
|
-
const
|
|
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
|
|
8
|
-
const serviceName = options.serviceName || process.env.APP_NAME
|
|
9
|
-
const environment = options.environment || process.env.NODE_ENV
|
|
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
|
-
|
|
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
|
+
}
|