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/README.md +8 -1
- package/index.js +38 -16
- package/middleware-express.js +218 -367
- package/middleware-restify.js +135 -306
- package/package.json +31 -29
- package/queue/fileQueue.js +100 -0
- package/queue/redisQueue.js +181 -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 +467 -0
- package/server.js +168 -70
- package/streams/bunyan.d.ts +26 -0
- package/streams/bunyan.js +39 -8
- package/streams/httpQueue.js +357 -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).',
|
|
@@ -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: {
|
|
@@ -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
|
}
|
|
@@ -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,90 @@ async function handleLog(req, res) {
|
|
|
952
957
|
}
|
|
953
958
|
}
|
|
954
959
|
|
|
955
|
-
|
|
956
|
-
if (typeof
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
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
|
+
}
|