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/register.js
CHANGED
|
@@ -4,16 +4,144 @@ if (process.env.AZIFY_LOGGER_DISABLE === '1') {
|
|
|
4
4
|
try {
|
|
5
5
|
const bunyan = require('bunyan')
|
|
6
6
|
const createBunyanStream = require('./streams/bunyan')
|
|
7
|
+
const { createHttpLoggerTransport } = require('./streams/httpQueue')
|
|
7
8
|
const { getRequestContext } = require('./store')
|
|
9
|
+
const { randomUUID, randomBytes } = require('crypto')
|
|
10
|
+
const { performance } = require('perf_hooks')
|
|
11
|
+
const { URL } = require('url')
|
|
12
|
+
const os = require('os')
|
|
13
|
+
|
|
14
|
+
const { shouldSample, markSource, HTTP_CLIENT_MODE } = require('./sampling')
|
|
15
|
+
|
|
16
|
+
const serviceName = process.env.APP_NAME
|
|
17
|
+
const environment = process.env.NODE_ENV
|
|
18
|
+
const loggerUrlFromEnv = process.env.AZIFY_LOGGER_URL
|
|
19
|
+
|
|
20
|
+
let loggerEndpoint
|
|
21
|
+
try {
|
|
22
|
+
loggerEndpoint = new URL(loggerUrlFromEnv)
|
|
23
|
+
} catch (_) {
|
|
24
|
+
loggerEndpoint = new URL('http://localhost:3001/log')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const loggerUrlString = loggerEndpoint.toString()
|
|
28
|
+
const transport = createHttpLoggerTransport(loggerUrlString)
|
|
29
|
+
|
|
30
|
+
const flushTransport = () => transport.flush().catch(() => {})
|
|
31
|
+
process.once('beforeExit', flushTransport)
|
|
32
|
+
process.once('exit', flushTransport)
|
|
33
|
+
|
|
34
|
+
const normalizedLoggerOrigin = `${loggerEndpoint.protocol}//${loggerEndpoint.host}`
|
|
35
|
+
const normalizedLoggerPath = normalizePath(loggerEndpoint.pathname || '/')
|
|
36
|
+
|
|
37
|
+
const SENSITIVE_HEADER_KEYS = new Set([
|
|
38
|
+
'authorization',
|
|
39
|
+
'cookie',
|
|
40
|
+
'set-cookie',
|
|
41
|
+
'x-api-key',
|
|
42
|
+
'x-auth-token',
|
|
43
|
+
'x-access-token',
|
|
44
|
+
'proxy-authorization'
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
function sanitizeHeaderEntries(headers) {
|
|
48
|
+
const sanitized = {}
|
|
49
|
+
if (!headers) {
|
|
50
|
+
return sanitized
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const assignValue = (key, value) => {
|
|
54
|
+
if (key == null) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
const lower = String(key).toLowerCase()
|
|
58
|
+
if (!lower) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
sanitized[lower] = SENSITIVE_HEADER_KEYS.has(lower) ? '***' : String(value)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof headers.forEach === 'function') {
|
|
65
|
+
headers.forEach((value, key) => assignValue(key, value))
|
|
66
|
+
} else if (Array.isArray(headers)) {
|
|
67
|
+
headers.forEach(([key, value]) => assignValue(key, value))
|
|
68
|
+
} else if (typeof headers === 'object') {
|
|
69
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
value.forEach((inner) => assignValue(key, inner))
|
|
72
|
+
} else if (value != null) {
|
|
73
|
+
assignValue(key, value)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return sanitized
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function sendOutboundLog(level, message, meta) {
|
|
82
|
+
const source = meta && meta.__source
|
|
83
|
+
if (!shouldSample(level, source)) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const payload = {
|
|
88
|
+
level,
|
|
89
|
+
message,
|
|
90
|
+
meta: {
|
|
91
|
+
...meta,
|
|
92
|
+
service: {
|
|
93
|
+
name: serviceName,
|
|
94
|
+
version: (meta && meta.service && meta.service.version) || '1.0.0'
|
|
95
|
+
},
|
|
96
|
+
environment,
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
hostname: os.hostname()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
transport.enqueue(payload, {
|
|
103
|
+
'content-type': 'application/json'
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizePath(path) {
|
|
108
|
+
if (!path) {
|
|
109
|
+
return '/'
|
|
110
|
+
}
|
|
111
|
+
const trimmed = String(path).replace(/\/+$/, '')
|
|
112
|
+
return trimmed === '' ? '/' : trimmed
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isLoggerApiCall(meta) {
|
|
116
|
+
if (!meta || !meta.url) {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const candidate = meta.url
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const target = new URL(candidate, normalizedLoggerOrigin)
|
|
124
|
+
const targetOrigin = `${target.protocol}//${target.host}`
|
|
125
|
+
const targetPath = normalizePath(target.pathname)
|
|
126
|
+
return targetOrigin === normalizedLoggerOrigin && targetPath === normalizedLoggerPath
|
|
127
|
+
} catch (_) {
|
|
128
|
+
if (typeof candidate === 'string') {
|
|
129
|
+
const relativePath = normalizePath(candidate)
|
|
130
|
+
return relativePath === normalizedLoggerPath
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
8
136
|
|
|
9
137
|
const originalCreate = bunyan.createLogger
|
|
10
138
|
bunyan.createLogger = function patchedCreateLogger(options) {
|
|
11
139
|
const logger = originalCreate.call(bunyan, options)
|
|
12
140
|
try {
|
|
13
141
|
const level = process.env.AZIFY_LOG_LEVEL || (options && options.level) || 'info'
|
|
14
|
-
const loggerUrl = process.env.AZIFY_LOGGER_URL
|
|
15
|
-
const serviceName = process.env.APP_NAME || (options && options.name)
|
|
16
|
-
const environment = process.env.NODE_ENV
|
|
142
|
+
const loggerUrl = process.env.AZIFY_LOGGER_URL
|
|
143
|
+
const serviceName = process.env.APP_NAME || (options && options.name)
|
|
144
|
+
const environment = process.env.NODE_ENV
|
|
17
145
|
|
|
18
146
|
logger.addStream({
|
|
19
147
|
level,
|
|
@@ -350,33 +478,170 @@ try {
|
|
|
350
478
|
|
|
351
479
|
try {
|
|
352
480
|
const axios = require('axios')
|
|
353
|
-
if (axios
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
481
|
+
if (axios) {
|
|
482
|
+
const { getRequestContext, runWithRequestContext } = require('./store')
|
|
483
|
+
|
|
484
|
+
const buildUrl = (config) => {
|
|
485
|
+
const url = config.url || ''
|
|
486
|
+
if (/^https?:\/\//i.test(url)) {
|
|
487
|
+
return url
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (config.baseURL) {
|
|
491
|
+
try {
|
|
492
|
+
return new URL(url, config.baseURL).toString()
|
|
493
|
+
} catch (_) {
|
|
494
|
+
const base = String(config.baseURL).replace(/\/$/, '')
|
|
495
|
+
const path = String(url).replace(/^\//, '')
|
|
496
|
+
return `${base}/${path}`
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return url
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const patchInstance = (instance) => {
|
|
504
|
+
if (instance.__azifyLoggerPatched) {
|
|
505
|
+
return instance
|
|
506
|
+
}
|
|
507
|
+
instance.__azifyLoggerPatched = true
|
|
508
|
+
|
|
509
|
+
if (HTTP_CLIENT_MODE === 'off') {
|
|
510
|
+
return instance
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
instance.interceptors.request.use(
|
|
514
|
+
(config) => {
|
|
515
|
+
const method = (config.method || 'get').toUpperCase()
|
|
516
|
+
const url = buildUrl(config)
|
|
517
|
+
const testMeta = { url }
|
|
518
|
+
if (isLoggerApiCall(testMeta)) {
|
|
519
|
+
return config
|
|
520
|
+
}
|
|
521
|
+
|
|
362
522
|
const ctx = getRequestContext()
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
523
|
+
const traceId = (ctx?.traceId) || randomUUID()
|
|
524
|
+
const parentSpanId = (ctx?.spanId) || null
|
|
525
|
+
const requestId = (ctx?.requestId) || randomUUID()
|
|
526
|
+
const spanId = randomBytes(8).toString('hex')
|
|
527
|
+
|
|
528
|
+
config.headers = {
|
|
529
|
+
...(config.headers || {}),
|
|
530
|
+
'x-trace-id': traceId,
|
|
531
|
+
'x-span-id': spanId,
|
|
532
|
+
'x-parent-span-id': parentSpanId || '',
|
|
533
|
+
'x-request-id': requestId
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const requestMeta = {
|
|
537
|
+
traceId,
|
|
538
|
+
spanId,
|
|
539
|
+
parentSpanId,
|
|
540
|
+
requestId,
|
|
541
|
+
method,
|
|
542
|
+
url,
|
|
543
|
+
headers: config.headers
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
markSource(requestMeta, 'http-client')
|
|
547
|
+
config.__azifyLogger = {
|
|
548
|
+
meta: requestMeta,
|
|
549
|
+
start: performance.now()
|
|
550
|
+
}
|
|
551
|
+
const childCtx = { traceId, spanId, parentSpanId, requestId }
|
|
552
|
+
config.__azifyChildCtx = childCtx
|
|
553
|
+
|
|
554
|
+
if (HTTP_CLIENT_MODE === 'all') {
|
|
555
|
+
sendOutboundLog('info', `[REQUEST] ${method} ${url}`, requestMeta)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return config
|
|
559
|
+
},
|
|
560
|
+
(error) => Promise.reject(error)
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
instance.interceptors.response.use(
|
|
564
|
+
(response) => {
|
|
565
|
+
const url = buildUrl(response.config)
|
|
566
|
+
const testMeta = { url }
|
|
567
|
+
if (isLoggerApiCall(testMeta)) {
|
|
568
|
+
return response
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const marker = response.config?.__azifyLogger
|
|
572
|
+
const childCtx = response.config?.__azifyChildCtx
|
|
573
|
+
|
|
574
|
+
if (marker && marker.meta) {
|
|
575
|
+
const duration = Number((performance.now() - marker.start).toFixed(2))
|
|
576
|
+
const shouldLogResponse =
|
|
577
|
+
HTTP_CLIENT_MODE === 'all' ||
|
|
578
|
+
(HTTP_CLIENT_MODE === 'errors' && response.status >= 400)
|
|
579
|
+
|
|
580
|
+
if (shouldLogResponse) {
|
|
581
|
+
const meta = {
|
|
582
|
+
...marker.meta,
|
|
583
|
+
statusCode: response.status,
|
|
584
|
+
responseTimeMs: duration,
|
|
585
|
+
responseHeaders: response.headers,
|
|
586
|
+
responseBody: response.data
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
markSource(meta, 'http-client')
|
|
590
|
+
const message = `[RESPONSE] ${meta.method} ${meta.url} ${response.status} ${duration}ms`
|
|
591
|
+
const level = response.status >= 500 ? 'error' : response.status >= 400 ? 'warn' : 'info'
|
|
592
|
+
|
|
593
|
+
sendOutboundLog(level, message, meta)
|
|
371
594
|
}
|
|
372
595
|
}
|
|
373
|
-
|
|
374
|
-
return
|
|
375
|
-
},
|
|
376
|
-
|
|
377
|
-
|
|
596
|
+
|
|
597
|
+
return response
|
|
598
|
+
},
|
|
599
|
+
(error) => {
|
|
600
|
+
const config = error?.config
|
|
601
|
+
if (config) {
|
|
602
|
+
const url = buildUrl(config)
|
|
603
|
+
const testMeta = { url }
|
|
604
|
+
if (isLoggerApiCall(testMeta)) {
|
|
605
|
+
return Promise.reject(error)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const marker = config?.__azifyLogger
|
|
610
|
+
const childCtx = config?.__azifyChildCtx
|
|
611
|
+
|
|
612
|
+
if (marker && marker.meta) {
|
|
613
|
+
const duration = Number((performance.now() - marker.start).toFixed(2))
|
|
614
|
+
const meta = {
|
|
615
|
+
...marker.meta,
|
|
616
|
+
responseTimeMs: duration,
|
|
617
|
+
error: {
|
|
618
|
+
name: error?.name || 'Error',
|
|
619
|
+
message: error?.message || String(error),
|
|
620
|
+
stack: error?.stack
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
markSource(meta, 'http-client')
|
|
624
|
+
const message = `[ERROR] ${meta.method} ${meta.url}`
|
|
625
|
+
|
|
626
|
+
sendOutboundLog('error', message, meta)
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return Promise.reject(error)
|
|
630
|
+
}
|
|
631
|
+
)
|
|
632
|
+
|
|
378
633
|
return instance
|
|
379
634
|
}
|
|
635
|
+
|
|
636
|
+
patchInstance(axios)
|
|
637
|
+
|
|
638
|
+
if (axios.create) {
|
|
639
|
+
const originalCreate = axios.create
|
|
640
|
+
axios.create = function(config) {
|
|
641
|
+
const instance = originalCreate.call(this, config)
|
|
642
|
+
return patchInstance(instance)
|
|
643
|
+
}
|
|
644
|
+
}
|
|
380
645
|
}
|
|
381
646
|
} catch (_) {}
|
|
382
647
|
|
|
@@ -387,63 +652,9 @@ try {
|
|
|
387
652
|
g.__azifyLoggerFetchPatched = true
|
|
388
653
|
|
|
389
654
|
const { getRequestContext, runWithRequestContext } = require('./store')
|
|
390
|
-
const { randomUUID, randomBytes } = require('crypto')
|
|
391
|
-
const { performance } = require('perf_hooks')
|
|
392
|
-
const axios = require('axios')
|
|
393
|
-
const os = require('os')
|
|
394
|
-
|
|
395
|
-
const serviceName = process.env.APP_NAME || 'app'
|
|
396
|
-
const loggerUrl = process.env.AZIFY_LOGGER_URL || 'http://localhost:3001/log'
|
|
397
|
-
const environment = process.env.NODE_ENV || 'development'
|
|
398
|
-
|
|
399
|
-
async function sendLog(level, message, meta) {
|
|
400
|
-
const payload = {
|
|
401
|
-
level,
|
|
402
|
-
message,
|
|
403
|
-
meta: {
|
|
404
|
-
...meta,
|
|
405
|
-
service: {
|
|
406
|
-
name: serviceName,
|
|
407
|
-
version: (meta && meta.service && meta.service.version) || '1.0.0'
|
|
408
|
-
},
|
|
409
|
-
environment,
|
|
410
|
-
timestamp: new Date().toISOString(),
|
|
411
|
-
hostname: os.hostname()
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
await axios.post(loggerUrl, payload, { timeout: 5000 })
|
|
417
|
-
} catch (error) {
|
|
418
|
-
console.error('Erro ao enviar log:', error && error.message ? error.message : error)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
655
|
|
|
422
656
|
const originalFetch = globalThis.fetch.bind(globalThis)
|
|
423
657
|
|
|
424
|
-
function sanitizeHeaders(headers) {
|
|
425
|
-
const SENSITIVE_HEADER_KEYS = new Set([
|
|
426
|
-
'authorization',
|
|
427
|
-
'cookie',
|
|
428
|
-
'set-cookie',
|
|
429
|
-
'x-api-key',
|
|
430
|
-
'x-auth-token',
|
|
431
|
-
'x-access-token',
|
|
432
|
-
'proxy-authorization'
|
|
433
|
-
])
|
|
434
|
-
|
|
435
|
-
const sanitized = {}
|
|
436
|
-
headers.forEach((value, key) => {
|
|
437
|
-
const lower = key.toLowerCase()
|
|
438
|
-
if (SENSITIVE_HEADER_KEYS.has(lower)) {
|
|
439
|
-
sanitized[lower] = '***'
|
|
440
|
-
} else {
|
|
441
|
-
sanitized[lower] = value
|
|
442
|
-
}
|
|
443
|
-
})
|
|
444
|
-
return sanitized
|
|
445
|
-
}
|
|
446
|
-
|
|
447
658
|
function ensureRequest(input, init) {
|
|
448
659
|
if (typeof Request !== 'undefined' && input instanceof Request) {
|
|
449
660
|
return init ? new Request(input, init) : input
|
|
@@ -452,14 +663,23 @@ try {
|
|
|
452
663
|
}
|
|
453
664
|
|
|
454
665
|
globalThis.fetch = async function patchedFetch(input, init) {
|
|
666
|
+
if (HTTP_CLIENT_MODE === 'off') {
|
|
667
|
+
return originalFetch(input, init)
|
|
668
|
+
}
|
|
669
|
+
|
|
455
670
|
const request = ensureRequest(input, init)
|
|
456
671
|
const method = request.method.toUpperCase()
|
|
457
672
|
const url = request.url
|
|
458
673
|
|
|
674
|
+
const testMeta = { url }
|
|
675
|
+
if (isLoggerApiCall(testMeta)) {
|
|
676
|
+
return originalFetch(request)
|
|
677
|
+
}
|
|
678
|
+
|
|
459
679
|
const ctx = getRequestContext()
|
|
460
|
-
const traceId = (ctx
|
|
461
|
-
const parentSpanId = (ctx
|
|
462
|
-
const requestId = (ctx
|
|
680
|
+
const traceId = (ctx?.traceId) || randomUUID()
|
|
681
|
+
const parentSpanId = (ctx?.spanId) || null
|
|
682
|
+
const requestId = (ctx?.requestId) || randomUUID()
|
|
463
683
|
const spanId = randomBytes(8).toString('hex')
|
|
464
684
|
|
|
465
685
|
request.headers.set('x-trace-id', traceId)
|
|
@@ -474,11 +694,13 @@ try {
|
|
|
474
694
|
requestId,
|
|
475
695
|
method,
|
|
476
696
|
url,
|
|
477
|
-
headers:
|
|
697
|
+
headers: Object.fromEntries(request.headers.entries())
|
|
478
698
|
}
|
|
479
699
|
|
|
480
|
-
|
|
481
|
-
|
|
700
|
+
markSource(requestMeta, 'http-client')
|
|
701
|
+
if (HTTP_CLIENT_MODE === 'all') {
|
|
702
|
+
sendOutboundLog('info', `[REQUEST] ${method} ${url}`, requestMeta)
|
|
703
|
+
}
|
|
482
704
|
|
|
483
705
|
const childCtx = {
|
|
484
706
|
traceId,
|
|
@@ -487,25 +709,67 @@ try {
|
|
|
487
709
|
requestId
|
|
488
710
|
}
|
|
489
711
|
|
|
712
|
+
const start = performance.now()
|
|
713
|
+
|
|
490
714
|
try {
|
|
491
715
|
const response = await runWithRequestContext(childCtx, function() {
|
|
492
716
|
return originalFetch(request)
|
|
493
717
|
})
|
|
494
718
|
|
|
495
719
|
const duration = Number((performance.now() - start).toFixed(2))
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
720
|
+
const shouldLogResponse =
|
|
721
|
+
HTTP_CLIENT_MODE === 'all' ||
|
|
722
|
+
(HTTP_CLIENT_MODE === 'errors' && response.status >= 400)
|
|
723
|
+
|
|
724
|
+
if (shouldLogResponse) {
|
|
725
|
+
(async () => {
|
|
726
|
+
try {
|
|
727
|
+
const clonedResponse = response.clone()
|
|
728
|
+
const contentType = response.headers.get('content-type') || ''
|
|
729
|
+
|
|
730
|
+
let responseBody = null
|
|
731
|
+
|
|
732
|
+
if (contentType.includes('application/json') || contentType.includes('text/')) {
|
|
733
|
+
const bodyText = await clonedResponse.text()
|
|
734
|
+
if (contentType.includes('application/json')) {
|
|
735
|
+
try {
|
|
736
|
+
responseBody = JSON.parse(bodyText)
|
|
737
|
+
} catch (_) {
|
|
738
|
+
responseBody = bodyText
|
|
739
|
+
}
|
|
740
|
+
} else {
|
|
741
|
+
responseBody = bodyText
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const responseMeta = {
|
|
746
|
+
...requestMeta,
|
|
747
|
+
statusCode: response.status,
|
|
748
|
+
responseTimeMs: duration,
|
|
749
|
+
responseHeaders: Object.fromEntries(response.headers.entries()),
|
|
750
|
+
responseBody: responseBody
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
markSource(responseMeta, 'http-client')
|
|
754
|
+
const message = `[RESPONSE] ${method} ${url} ${response.status} ${duration}ms`
|
|
755
|
+
|
|
756
|
+
const level = response.status >= 500 ? 'error' : response.status >= 400 ? 'warn' : 'info'
|
|
757
|
+
sendOutboundLog(level, message, responseMeta)
|
|
758
|
+
} catch (_) {
|
|
759
|
+
const responseMeta = {
|
|
760
|
+
...requestMeta,
|
|
761
|
+
statusCode: response.status,
|
|
762
|
+
responseTimeMs: duration,
|
|
763
|
+
responseHeaders: Object.fromEntries(response.headers.entries()),
|
|
764
|
+
responseBody: null
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
markSource(responseMeta, 'http-client')
|
|
768
|
+
const message = `[RESPONSE] ${method} ${url} ${response.status} ${duration}ms`
|
|
769
|
+
const level = response.status >= 500 ? 'error' : response.status >= 400 ? 'warn' : 'info'
|
|
770
|
+
sendOutboundLog(level, message, responseMeta)
|
|
771
|
+
}
|
|
772
|
+
})()
|
|
509
773
|
}
|
|
510
774
|
|
|
511
775
|
return response
|
|
@@ -515,13 +779,14 @@ try {
|
|
|
515
779
|
...requestMeta,
|
|
516
780
|
responseTimeMs: duration
|
|
517
781
|
}
|
|
782
|
+
markSource(errorMeta, 'http-client')
|
|
518
783
|
const err = error instanceof Error ? error : new Error(String(error))
|
|
519
784
|
errorMeta.error = {
|
|
520
785
|
name: err.name,
|
|
521
786
|
message: err.message,
|
|
522
787
|
stack: err.stack
|
|
523
788
|
}
|
|
524
|
-
void
|
|
789
|
+
void sendOutboundLog('error', `[ERROR] ${method} ${url}`, errorMeta)
|
|
525
790
|
throw error
|
|
526
791
|
}
|
|
527
792
|
}
|
package/sampling.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const clampRate = (value) => {
|
|
2
|
+
if (value === null || value === undefined || value === '') {
|
|
3
|
+
return null
|
|
4
|
+
}
|
|
5
|
+
const numeric = Number(value)
|
|
6
|
+
if (!Number.isFinite(numeric)) {
|
|
7
|
+
return null
|
|
8
|
+
}
|
|
9
|
+
return Math.max(0, Math.min(1, numeric))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const defaultSampleRate = clampRate(process.env.AZIFY_LOGGER_SAMPLE_RATE) ?? 1
|
|
13
|
+
|
|
14
|
+
const resolveSampleRate = (envName, fallback) => {
|
|
15
|
+
const parsed = clampRate(process.env[envName])
|
|
16
|
+
return parsed === null ? fallback : parsed
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const levelSampleRates = {
|
|
20
|
+
default: defaultSampleRate,
|
|
21
|
+
fatal: resolveSampleRate('AZIFY_LOGGER_SAMPLE_RATE_FATAL', defaultSampleRate),
|
|
22
|
+
error: resolveSampleRate('AZIFY_LOGGER_SAMPLE_RATE_ERROR', defaultSampleRate),
|
|
23
|
+
warn: resolveSampleRate('AZIFY_LOGGER_SAMPLE_RATE_WARN', defaultSampleRate),
|
|
24
|
+
info: resolveSampleRate('AZIFY_LOGGER_SAMPLE_RATE_INFO', defaultSampleRate),
|
|
25
|
+
debug: resolveSampleRate(
|
|
26
|
+
'AZIFY_LOGGER_SAMPLE_RATE_DEBUG',
|
|
27
|
+
defaultSampleRate < 1 ? defaultSampleRate : 0.05
|
|
28
|
+
),
|
|
29
|
+
trace: resolveSampleRate(
|
|
30
|
+
'AZIFY_LOGGER_SAMPLE_RATE_TRACE',
|
|
31
|
+
defaultSampleRate < 1 ? defaultSampleRate : 0.02
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const HTTP_CLIENT_MODE = (process.env.AZIFY_LOGGER_HTTP_CLIENT_LOGGING || 'all').toLowerCase()
|
|
36
|
+
|
|
37
|
+
const httpClientSampleRate = resolveSampleRate(
|
|
38
|
+
'AZIFY_LOGGER_HTTP_SAMPLE_RATE',
|
|
39
|
+
HTTP_CLIENT_MODE === 'all' ? 1 : 1 // Sempre 1.0 (100%) quando HTTP_CLIENT_MODE === 'all'
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const sourceSampleRates = {
|
|
43
|
+
'http-client': httpClientSampleRate,
|
|
44
|
+
logger: resolveSampleRate('AZIFY_LOGGER_SAMPLE_RATE_LOGGER', defaultSampleRate)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const shouldSample = (level, source) => {
|
|
48
|
+
const key = typeof level === 'string' ? level.toLowerCase() : 'info'
|
|
49
|
+
let rate = levelSampleRates[key] ?? levelSampleRates.default
|
|
50
|
+
if (source && sourceSampleRates[source] !== undefined) {
|
|
51
|
+
rate = Math.min(rate, sourceSampleRates[source])
|
|
52
|
+
}
|
|
53
|
+
if (rate >= 1) {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
if (rate <= 0) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
return Math.random() < rate
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const markSource = (meta, source) => {
|
|
63
|
+
if (!meta || typeof meta !== 'object' || !source) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
Object.defineProperty(meta, '__source', {
|
|
67
|
+
value: source,
|
|
68
|
+
enumerable: false,
|
|
69
|
+
configurable: false
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
shouldSample,
|
|
75
|
+
markSource,
|
|
76
|
+
HTTP_CLIENT_MODE
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|