azify-logger 1.0.17 → 1.0.19
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 +5 -6
- package/index.js +17 -4
- package/init.js +4 -2
- package/middleware-express.js +2 -2
- package/package.json +11 -9
- package/register-otel.js +1 -9
- package/register.js +5 -77
- package/server.js +147 -77
- package/streams/bunyan.js +2 -2
- package/streams/pino.js +17 -5
package/README.md
CHANGED
|
@@ -86,9 +86,7 @@ const app = express()
|
|
|
86
86
|
|----------|-----------------------------------|-----------|
|
|
87
87
|
| `APP_NAME` | - | Nome da aplicação |
|
|
88
88
|
| `AZIFY_LOGGER_URL` | `http://localhost:3001/log` | URL do logger |
|
|
89
|
-
| `
|
|
90
|
-
| `NODE_ENV` | `development` | Ambiente |
|
|
91
|
-
| `AZIFY_LOGGER_AUTOREG_DISABLE` | `""` | Se `"1"`, desativa auto-registro do OTEL |
|
|
89
|
+
| `NODE_ENV` | `development` | Ambiente |
|
|
92
90
|
|
|
93
91
|
### 🌐 URLs por Ambiente
|
|
94
92
|
|
|
@@ -102,14 +100,14 @@ NODE_ENV=development
|
|
|
102
100
|
**Staging:**
|
|
103
101
|
```env
|
|
104
102
|
APP_NAME=minha-app
|
|
105
|
-
AZIFY_LOGGER_URL=
|
|
103
|
+
AZIFY_LOGGER_URL=https://logsdashboard.azify.dev/send
|
|
106
104
|
NODE_ENV=staging
|
|
107
105
|
```
|
|
108
106
|
|
|
109
107
|
**Production:**
|
|
110
108
|
```env
|
|
111
109
|
APP_NAME=minha-app
|
|
112
|
-
AZIFY_LOGGER_URL=
|
|
110
|
+
AZIFY_LOGGER_URL=https://logsdashboard.azify.prd/send (a conigurar)
|
|
113
111
|
NODE_ENV=production
|
|
114
112
|
```
|
|
115
113
|
|
|
@@ -167,6 +165,8 @@ if (createAzifyBunyanStream) {
|
|
|
167
165
|
stream: createAzifyBunyanStream({
|
|
168
166
|
loggerUrl: process.env.AZIFY_LOGGER_URL || 'http://localhost:3001/log',
|
|
169
167
|
serviceName: process.env.APP_NAME || 'app'
|
|
168
|
+
loggerUrl: process.env.AZIFY_LOGGER_URL,
|
|
169
|
+
serviceName: process.env.APP_NAME
|
|
170
170
|
})
|
|
171
171
|
})
|
|
172
172
|
}
|
|
@@ -220,7 +220,6 @@ logger.error('Erro', new Error('Falha'), { context: 'payment' })
|
|
|
220
220
|
|
|
221
221
|
Se você precisa subir a infraestrutura do azify-logger:
|
|
222
222
|
|
|
223
|
-
### 1. Iniciar serviços
|
|
224
223
|
|
|
225
224
|
```bash
|
|
226
225
|
./start-docker.sh
|
package/index.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
const axios = require('axios')
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
let context, propagation, trace, W3CTraceContextPropagator
|
|
4
|
+
try {
|
|
5
|
+
const otelApi = require('@opentelemetry/api')
|
|
6
|
+
const otelCore = require('@opentelemetry/core')
|
|
7
|
+
context = otelApi.context
|
|
8
|
+
propagation = otelApi.propagation
|
|
9
|
+
trace = otelApi.trace
|
|
10
|
+
W3CTraceContextPropagator = otelCore.W3CTraceContextPropagator
|
|
11
|
+
} catch (_) {
|
|
12
|
+
context = { active: () => ({}) }
|
|
13
|
+
propagation = { setGlobalPropagator: () => {} }
|
|
14
|
+
trace = { getSpan: () => null }
|
|
15
|
+
W3CTraceContextPropagator = class {}
|
|
16
|
+
}
|
|
4
17
|
|
|
5
18
|
if (process.env.AZIFY_LOGGER_AUTOREG_DISABLE !== '1') {
|
|
6
19
|
try { require('./register-otel') } catch (_) {}
|
|
@@ -41,7 +54,7 @@ class AzifyLogger {
|
|
|
41
54
|
*/
|
|
42
55
|
async log(level, message, meta = {}) {
|
|
43
56
|
const span = trace.getSpan(context.active())
|
|
44
|
-
const spanContext = span
|
|
57
|
+
const spanContext = span && span.spanContext()
|
|
45
58
|
|
|
46
59
|
const logData = {
|
|
47
60
|
level,
|
|
@@ -50,7 +63,7 @@ class AzifyLogger {
|
|
|
50
63
|
...meta,
|
|
51
64
|
service: {
|
|
52
65
|
name: this.options.serviceName,
|
|
53
|
-
version: meta.service
|
|
66
|
+
version: (meta.service && meta.service.version) || '1.0.0'
|
|
54
67
|
},
|
|
55
68
|
environment: this.options.environment,
|
|
56
69
|
timestamp: new Date().toISOString(),
|
package/init.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
if (process.env.AZIFY_LOGGER_DISABLE === '1')
|
|
2
|
-
|
|
1
|
+
if (process.env.AZIFY_LOGGER_DISABLE === '1') {
|
|
2
|
+
module.exports = {}
|
|
3
|
+
} else {
|
|
3
4
|
const Module = require('module')
|
|
4
5
|
const originalRequire = Module.prototype.require
|
|
5
6
|
|
|
@@ -70,3 +71,4 @@ Module.prototype.require = function(id) {
|
|
|
70
71
|
|
|
71
72
|
return result
|
|
72
73
|
}
|
|
74
|
+
}
|
package/middleware-express.js
CHANGED
|
@@ -186,8 +186,8 @@ function createExpressLoggingMiddleware(options = {}) {
|
|
|
186
186
|
path: req.url,
|
|
187
187
|
headers: sanitizeHeaders(req.headers || {}),
|
|
188
188
|
query: req.query || {},
|
|
189
|
-
userAgent: req.headers
|
|
190
|
-
ip: req.connection
|
|
189
|
+
userAgent: (req.headers && req.headers['user-agent']) || 'unknown',
|
|
190
|
+
ip: (req.connection && req.connection.remoteAddress) || (req.socket && req.socket.remoteAddress) || req.ip || 'unknown',
|
|
191
191
|
traceId: requestTraceId,
|
|
192
192
|
spanId: requestSpanId,
|
|
193
193
|
parentSpanId: requestParentSpanId
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azify-logger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "Azify Logger Client - Centralized logging for OpenSearch",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,13 +27,6 @@
|
|
|
27
27
|
"author": "Azify",
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@opentelemetry/api": "^1.7.0",
|
|
31
|
-
"@opentelemetry/auto-instrumentations-node": "^0.40.0",
|
|
32
|
-
"@opentelemetry/core": "^1.28.0",
|
|
33
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.45.0",
|
|
34
|
-
"@opentelemetry/resources": "^1.28.0",
|
|
35
|
-
"@opentelemetry/sdk-node": "^0.45.0",
|
|
36
|
-
"@opentelemetry/semantic-conventions": "^1.28.0",
|
|
37
30
|
"axios": "^1.6.0",
|
|
38
31
|
"cors": "^2.8.5",
|
|
39
32
|
"express": "^4.18.2",
|
|
@@ -43,8 +36,17 @@
|
|
|
43
36
|
"require-in-the-middle": "^7.4.0",
|
|
44
37
|
"uuid": "^9.0.1"
|
|
45
38
|
},
|
|
39
|
+
"optionalDependencies": {
|
|
40
|
+
"@opentelemetry/api": "^1.7.0",
|
|
41
|
+
"@opentelemetry/auto-instrumentations-node": "^0.40.0",
|
|
42
|
+
"@opentelemetry/core": "^1.28.0",
|
|
43
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.45.0",
|
|
44
|
+
"@opentelemetry/resources": "^1.28.0",
|
|
45
|
+
"@opentelemetry/sdk-node": "^0.45.0",
|
|
46
|
+
"@opentelemetry/semantic-conventions": "^1.28.0"
|
|
47
|
+
},
|
|
46
48
|
"engines": {
|
|
47
|
-
"node": ">=
|
|
49
|
+
"node": ">=12.0.0"
|
|
48
50
|
},
|
|
49
51
|
"files": [
|
|
50
52
|
"index.js",
|
package/register-otel.js
CHANGED
|
@@ -8,8 +8,6 @@ try {
|
|
|
8
8
|
process.env.OTEL_SERVICE_NAME = serviceName
|
|
9
9
|
process.env.OTEL_SERVICE_VERSION = serviceVersion
|
|
10
10
|
|
|
11
|
-
console.log(`🔧 Initializing OpenTelemetry for service: ${serviceName}`)
|
|
12
|
-
|
|
13
11
|
const sdk = new NodeSDK({
|
|
14
12
|
serviceName: serviceName,
|
|
15
13
|
serviceVersion: serviceVersion,
|
|
@@ -25,15 +23,9 @@ try {
|
|
|
25
23
|
})
|
|
26
24
|
|
|
27
25
|
sdk.start()
|
|
28
|
-
console.log(`✅ OpenTelemetry initialized for service: ${serviceName}`)
|
|
29
26
|
|
|
30
27
|
process.once('SIGTERM', () => {
|
|
31
28
|
sdk.shutdown()
|
|
32
|
-
.then(() => console.log('OpenTelemetry shut down successfully'))
|
|
33
|
-
.catch((error) => console.error('Error shutting down OpenTelemetry:', error))
|
|
34
29
|
.finally(() => process.exit(0))
|
|
35
30
|
})
|
|
36
|
-
} catch (
|
|
37
|
-
console.warn('⚠️ OpenTelemetry não pôde ser inicializado:', error.message)
|
|
38
|
-
console.error('Error details:', error)
|
|
39
|
-
}
|
|
31
|
+
} catch (_) {}
|
package/register.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
if (process.env.AZIFY_LOGGER_DISABLE === '1')
|
|
2
|
-
|
|
1
|
+
if (process.env.AZIFY_LOGGER_DISABLE === '1') {
|
|
2
|
+
module.exports = {}
|
|
3
|
+
} else {
|
|
3
4
|
try {
|
|
4
5
|
const bunyan = require('bunyan')
|
|
5
6
|
const createBunyanStream = require('./streams/bunyan')
|
|
@@ -56,18 +57,6 @@ try {
|
|
|
56
57
|
obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
// else {
|
|
60
|
-
// const messageStr = typeof obj === 'string' ? obj : (typeof msg === 'string' ? msg : '')
|
|
61
|
-
// const traceMatch = messageStr.match(/\[([a-f0-9-]{8}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{12})\]/)
|
|
62
|
-
// if (traceMatch) {
|
|
63
|
-
// const extractedTraceId = traceMatch[1]
|
|
64
|
-
// if (typeof obj === 'object' && obj !== null) {
|
|
65
|
-
// obj.traceId = extractedTraceId
|
|
66
|
-
// } else {
|
|
67
|
-
// obj = { traceId: extractedTraceId, msg: obj }
|
|
68
|
-
// }
|
|
69
|
-
// }
|
|
70
|
-
// }
|
|
71
60
|
return originalInfo.call(this, obj, msg, ...args)
|
|
72
61
|
}
|
|
73
62
|
|
|
@@ -83,18 +72,6 @@ try {
|
|
|
83
72
|
obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
|
|
84
73
|
}
|
|
85
74
|
}
|
|
86
|
-
// else {
|
|
87
|
-
// const messageStr = typeof obj === 'string' ? obj : (typeof msg === 'string' ? msg : '')
|
|
88
|
-
// const traceMatch = messageStr.match(/\[([a-f0-9-]{8}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{12})\]/)
|
|
89
|
-
// if (traceMatch) {
|
|
90
|
-
// const extractedTraceId = traceMatch[1]
|
|
91
|
-
// if (typeof obj === 'object' && obj !== null) {
|
|
92
|
-
// obj.traceId = extractedTraceId
|
|
93
|
-
// } else {
|
|
94
|
-
// obj = { traceId: extractedTraceId, msg: obj }
|
|
95
|
-
// }
|
|
96
|
-
// }
|
|
97
|
-
// }
|
|
98
75
|
return originalError.call(this, obj, msg, ...args)
|
|
99
76
|
}
|
|
100
77
|
|
|
@@ -110,18 +87,6 @@ try {
|
|
|
110
87
|
obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
|
|
111
88
|
}
|
|
112
89
|
}
|
|
113
|
-
// else {
|
|
114
|
-
// const messageStr = typeof obj === 'string' ? obj : (typeof msg === 'string' ? msg : '')
|
|
115
|
-
// const traceMatch = messageStr.match(/\[([a-f0-9-]{8}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{12})\]/)
|
|
116
|
-
// if (traceMatch) {
|
|
117
|
-
// const extractedTraceId = traceMatch[1]
|
|
118
|
-
// if (typeof obj === 'object' && obj !== null) {
|
|
119
|
-
// obj.traceId = extractedTraceId
|
|
120
|
-
// } else {
|
|
121
|
-
// obj = { traceId: extractedTraceId, msg: obj }
|
|
122
|
-
// }
|
|
123
|
-
// }
|
|
124
|
-
// }
|
|
125
90
|
return originalWarn.call(this, obj, msg, ...args)
|
|
126
91
|
}
|
|
127
92
|
|
|
@@ -137,19 +102,6 @@ try {
|
|
|
137
102
|
obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
|
|
138
103
|
}
|
|
139
104
|
}
|
|
140
|
-
// else {
|
|
141
|
-
// // Extract traceId from message if it exists
|
|
142
|
-
// const messageStr = typeof obj === 'string' ? obj : (typeof msg === 'string' ? msg : '')
|
|
143
|
-
// const traceMatch = messageStr.match(/\[([a-f0-9-]{8}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{12})\]/)
|
|
144
|
-
// if (traceMatch) {
|
|
145
|
-
// const extractedTraceId = traceMatch[1]
|
|
146
|
-
// if (typeof obj === 'object' && obj !== null) {
|
|
147
|
-
// obj.traceId = extractedTraceId
|
|
148
|
-
// } else {
|
|
149
|
-
// obj = { traceId: extractedTraceId, msg: obj }
|
|
150
|
-
// }
|
|
151
|
-
// }
|
|
152
|
-
// }
|
|
153
105
|
return originalDebug.call(this, obj, msg, ...args)
|
|
154
106
|
}
|
|
155
107
|
|
|
@@ -165,18 +117,6 @@ try {
|
|
|
165
117
|
obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
|
|
166
118
|
}
|
|
167
119
|
}
|
|
168
|
-
// else {
|
|
169
|
-
// const messageStr = typeof obj === 'string' ? obj : (typeof msg === 'string' ? msg : '')
|
|
170
|
-
// const traceMatch = messageStr.match(/\[([a-f0-9-]{8}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{12})\]/)
|
|
171
|
-
// if (traceMatch) {
|
|
172
|
-
// const extractedTraceId = traceMatch[1]
|
|
173
|
-
// if (typeof obj === 'object' && obj !== null) {
|
|
174
|
-
// obj.traceId = extractedTraceId
|
|
175
|
-
// } else {
|
|
176
|
-
// obj = { traceId: extractedTraceId, msg: obj }
|
|
177
|
-
// }
|
|
178
|
-
// }
|
|
179
|
-
// }
|
|
180
120
|
return originalFatal.call(this, obj, msg, ...args)
|
|
181
121
|
}
|
|
182
122
|
|
|
@@ -192,19 +132,6 @@ try {
|
|
|
192
132
|
obj = { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId, msg: obj }
|
|
193
133
|
}
|
|
194
134
|
}
|
|
195
|
-
// else {
|
|
196
|
-
// // Extract traceId from message if it exists
|
|
197
|
-
// const messageStr = typeof obj === 'string' ? obj : (typeof msg === 'string' ? msg : '')
|
|
198
|
-
// const traceMatch = messageStr.match(/\[([a-f0-9-]{8}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{4}-[a-f0-9-]{12})\]/)
|
|
199
|
-
// if (traceMatch) {
|
|
200
|
-
// const extractedTraceId = traceMatch[1]
|
|
201
|
-
// if (typeof obj === 'object' && obj !== null) {
|
|
202
|
-
// obj.traceId = extractedTraceId
|
|
203
|
-
// } else {
|
|
204
|
-
// obj = { traceId: extractedTraceId, msg: obj }
|
|
205
|
-
// }
|
|
206
|
-
// }
|
|
207
|
-
// }
|
|
208
135
|
return originalTrace.call(this, obj, msg, ...args)
|
|
209
136
|
}
|
|
210
137
|
|
|
@@ -403,7 +330,7 @@ try {
|
|
|
403
330
|
const ctx = getRequestContext()
|
|
404
331
|
|
|
405
332
|
if (ctx && ctx.traceId) {
|
|
406
|
-
const headers = options
|
|
333
|
+
const headers = (options && options.headers) || {}
|
|
407
334
|
options = {
|
|
408
335
|
...options,
|
|
409
336
|
headers: {
|
|
@@ -526,3 +453,4 @@ try {
|
|
|
526
453
|
}
|
|
527
454
|
} catch (_) {}
|
|
528
455
|
} catch (_) {}
|
|
456
|
+
}
|
package/server.js
CHANGED
|
@@ -4,14 +4,27 @@ const axios = require('axios')
|
|
|
4
4
|
const express = require('express')
|
|
5
5
|
const cors = require('cors')
|
|
6
6
|
const os = require('os')
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
let trace, context, propagation, W3CTraceContextPropagator
|
|
8
|
+
try {
|
|
9
|
+
const otelApi = require('@opentelemetry/api')
|
|
10
|
+
const otelCore = require('@opentelemetry/core')
|
|
11
|
+
trace = otelApi.trace
|
|
12
|
+
context = otelApi.context
|
|
13
|
+
propagation = otelApi.propagation
|
|
14
|
+
W3CTraceContextPropagator = otelCore.W3CTraceContextPropagator
|
|
15
|
+
} catch (_) {
|
|
16
|
+
trace = { getSpan: () => null }
|
|
17
|
+
context = { active: () => ({}) }
|
|
18
|
+
propagation = { extract: () => ({}), inject: () => {} }
|
|
19
|
+
W3CTraceContextPropagator = class {}
|
|
20
|
+
}
|
|
9
21
|
|
|
10
22
|
const app = express()
|
|
11
23
|
|
|
12
24
|
app.set('trust proxy', 1)
|
|
13
25
|
|
|
14
|
-
app.use(express.json())
|
|
26
|
+
app.use(express.json({ limit: '10mb' }))
|
|
27
|
+
app.use(express.urlencoded({ extended: true, limit: '10mb' }))
|
|
15
28
|
app.use(cors())
|
|
16
29
|
|
|
17
30
|
const authEnabled = process.env.AZURE_AD_AUTH_ENABLED === 'true'
|
|
@@ -33,79 +46,121 @@ const propagator = new W3CTraceContextPropagator()
|
|
|
33
46
|
const traceContextMap = new Map()
|
|
34
47
|
|
|
35
48
|
/**
|
|
36
|
-
*
|
|
49
|
+
* Creates an index template for dynamic log indices
|
|
50
|
+
* This allows OpenSearch to auto-create indices like logs-azipay, logs-assemble, etc.
|
|
37
51
|
* @returns {Promise<void>}
|
|
38
52
|
* @private
|
|
39
53
|
*/
|
|
40
|
-
async function
|
|
41
|
-
const
|
|
54
|
+
async function ensureIndexTemplate() {
|
|
55
|
+
const templateName = 'logs-template'
|
|
42
56
|
const osUrl = process.env.OPENSEARCH_URL || 'http://localhost:9200'
|
|
57
|
+
|
|
43
58
|
try {
|
|
44
|
-
await axios.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
statusCode: { type: 'integer' },
|
|
84
|
-
responseTime: { type: 'float' },
|
|
85
|
-
ip: { type: 'ip' },
|
|
86
|
-
userAgent: { type: 'text' },
|
|
87
|
-
environment: { type: 'keyword' },
|
|
88
|
-
hostname: { type: 'keyword' },
|
|
89
|
-
responseBody: { type: 'text' },
|
|
90
|
-
error: {
|
|
91
|
-
properties: {
|
|
92
|
-
message: { type: 'text' },
|
|
93
|
-
stack: { type: 'text' },
|
|
94
|
-
name: { type: 'keyword' }
|
|
95
|
-
}
|
|
59
|
+
await axios.put(`${osUrl}/_index_template/${templateName}`, {
|
|
60
|
+
index_patterns: ['logs-*'],
|
|
61
|
+
template: {
|
|
62
|
+
settings: {
|
|
63
|
+
number_of_shards: 1,
|
|
64
|
+
number_of_replicas: 0,
|
|
65
|
+
'index.refresh_interval': '5s'
|
|
66
|
+
},
|
|
67
|
+
mappings: {
|
|
68
|
+
properties: {
|
|
69
|
+
timestamp: { type: 'date' },
|
|
70
|
+
level: { type: 'keyword' },
|
|
71
|
+
message: { type: 'text' },
|
|
72
|
+
service: {
|
|
73
|
+
properties: {
|
|
74
|
+
name: { type: 'keyword' },
|
|
75
|
+
version: { type: 'keyword' }
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
appName: { type: 'keyword' },
|
|
79
|
+
traceId: { type: 'keyword' },
|
|
80
|
+
spanId: { type: 'keyword' },
|
|
81
|
+
parentSpanId: { type: 'keyword' },
|
|
82
|
+
userId: { type: 'keyword' },
|
|
83
|
+
requestId: { type: 'keyword' },
|
|
84
|
+
method: { type: 'keyword' },
|
|
85
|
+
url: { type: 'keyword' },
|
|
86
|
+
statusCode: { type: 'integer' },
|
|
87
|
+
responseTime: { type: 'float' },
|
|
88
|
+
ip: { type: 'ip' },
|
|
89
|
+
userAgent: { type: 'text' },
|
|
90
|
+
environment: { type: 'keyword' },
|
|
91
|
+
hostname: { type: 'keyword' },
|
|
92
|
+
responseBody: { type: 'text' },
|
|
93
|
+
error: {
|
|
94
|
+
properties: {
|
|
95
|
+
message: { type: 'text' },
|
|
96
|
+
stack: { type: 'text' },
|
|
97
|
+
name: { type: 'keyword' }
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
priority: 500
|
|
104
|
+
})
|
|
105
|
+
console.log(`✅ Index template ${templateName} criado/atualizado no OpenSearch`)
|
|
106
|
+
console.log(` Índices serão criados automaticamente no formato: logs-{service-name}`)
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('❌ Erro ao criar index template:', error.message)
|
|
109
|
+
if (error.response) {
|
|
110
|
+
console.error(' Detalhes:', error.response.data)
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
|
|
115
|
+
ensureIndexTemplate()
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Escapes HTML special characters to prevent XSS attacks
|
|
119
|
+
* This prevents script injection when data is rendered in OpenSearch Dashboards
|
|
120
|
+
* @param {string} str - String to escape
|
|
121
|
+
* @returns {string} Escaped string
|
|
122
|
+
*/
|
|
123
|
+
function escapeHtml(str) {
|
|
124
|
+
if (typeof str !== 'string') return str
|
|
125
|
+
const map = {
|
|
126
|
+
'&': '&',
|
|
127
|
+
'<': '<',
|
|
128
|
+
'>': '>',
|
|
129
|
+
'"': '"',
|
|
130
|
+
"'": ''',
|
|
131
|
+
'/': '/'
|
|
132
|
+
}
|
|
133
|
+
return str.replace(/[&<>"'/]/g, (m) => map[m])
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sanitizes a value recursively to prevent XSS attacks
|
|
138
|
+
* Escapes HTML special characters in strings that could be rendered in Dashboards
|
|
139
|
+
* @param {any} value - Value to sanitize
|
|
140
|
+
* @returns {any} Sanitized value
|
|
141
|
+
*/
|
|
142
|
+
function sanitizeValue(value) {
|
|
143
|
+
if (typeof value === 'string') {
|
|
144
|
+
// Escape HTML special characters to prevent XSS when rendered in Dashboards
|
|
145
|
+
return escapeHtml(value)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
return value.map(item => sanitizeValue(item))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (value !== null && typeof value === 'object') {
|
|
153
|
+
const sanitized = {}
|
|
154
|
+
for (const key in value) {
|
|
155
|
+
if (value.hasOwnProperty(key)) {
|
|
156
|
+
sanitized[key] = sanitizeValue(value[key])
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return sanitized
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return value
|
|
163
|
+
}
|
|
109
164
|
|
|
110
165
|
function generateTraceId() {
|
|
111
166
|
return Math.random().toString(36).substring(2, 15) +
|
|
@@ -162,7 +217,18 @@ app.get('/', ensureAuthenticated, (req, res) => {
|
|
|
162
217
|
})
|
|
163
218
|
|
|
164
219
|
app.post('/log', async (req, res) => {
|
|
165
|
-
|
|
220
|
+
let { level, message, meta } = req.body
|
|
221
|
+
|
|
222
|
+
if (typeof message === 'string') {
|
|
223
|
+
message = sanitizeValue(message)
|
|
224
|
+
}
|
|
225
|
+
if (meta) {
|
|
226
|
+
meta = sanitizeValue(meta)
|
|
227
|
+
}
|
|
228
|
+
if (typeof level === 'string') {
|
|
229
|
+
level = sanitizeValue(level)
|
|
230
|
+
}
|
|
231
|
+
|
|
166
232
|
if (!level || !message) {
|
|
167
233
|
return res.status(400).json({ success: false, message: 'Level and message are required.' })
|
|
168
234
|
}
|
|
@@ -183,11 +249,11 @@ app.post('/log', async (req, res) => {
|
|
|
183
249
|
return res.json({ success: true, message: 'Log filtrado (Prisma verboso)' })
|
|
184
250
|
}
|
|
185
251
|
|
|
186
|
-
const requestId = meta
|
|
252
|
+
const requestId = meta && meta.requestId
|
|
187
253
|
|
|
188
254
|
let traceContext = null
|
|
189
255
|
|
|
190
|
-
if (meta
|
|
256
|
+
if (meta && meta.traceId && meta.spanId) {
|
|
191
257
|
traceContext = {
|
|
192
258
|
traceId: meta.traceId,
|
|
193
259
|
spanId: meta.spanId,
|
|
@@ -206,7 +272,8 @@ app.post('/log', async (req, res) => {
|
|
|
206
272
|
}
|
|
207
273
|
})
|
|
208
274
|
|
|
209
|
-
const
|
|
275
|
+
const span = trace.getSpan(extractedCtx)
|
|
276
|
+
const spanContext = (span && span.spanContext && span.spanContext()) || null
|
|
210
277
|
if (spanContext && spanContext.traceId && spanContext.spanId) {
|
|
211
278
|
traceContext = {
|
|
212
279
|
traceId: spanContext.traceId,
|
|
@@ -230,16 +297,16 @@ app.post('/log', async (req, res) => {
|
|
|
230
297
|
}
|
|
231
298
|
|
|
232
299
|
const logEntry = {
|
|
233
|
-
timestamp: meta
|
|
300
|
+
timestamp: (meta && meta.timestamp) || new Date().toISOString(),
|
|
234
301
|
level,
|
|
235
302
|
message,
|
|
236
303
|
service: {
|
|
237
|
-
name: meta
|
|
238
|
-
version: meta
|
|
304
|
+
name: (meta && meta.service && meta.service.name) || 'unknown-service',
|
|
305
|
+
version: (meta && meta.service && meta.service.version) || '1.0.0'
|
|
239
306
|
},
|
|
240
|
-
appName: meta
|
|
241
|
-
environment: meta
|
|
242
|
-
hostname: meta
|
|
307
|
+
appName: (meta && meta.appName) || (meta && meta.service && meta.service.name) || undefined,
|
|
308
|
+
environment: (meta && meta.environment) || process.env.NODE_ENV || 'development',
|
|
309
|
+
hostname: (meta && meta.hostname) || os.hostname(),
|
|
243
310
|
traceId: traceContext.traceId,
|
|
244
311
|
spanId: traceContext.spanId,
|
|
245
312
|
parentSpanId: traceContext.parentSpanId
|
|
@@ -255,12 +322,15 @@ app.post('/log', async (req, res) => {
|
|
|
255
322
|
|
|
256
323
|
try {
|
|
257
324
|
const osUrl = process.env.OPENSEARCH_URL || 'http://localhost:9200'
|
|
258
|
-
|
|
325
|
+
const serviceName = (logEntry.service.name || 'unknown').toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
|
326
|
+
const indexName = `logs-${serviceName}`
|
|
327
|
+
|
|
328
|
+
await axios.post(`${osUrl}/${indexName}/_doc`, logEntry, {
|
|
259
329
|
headers: { 'Content-Type': 'application/json' }
|
|
260
330
|
})
|
|
261
331
|
|
|
262
|
-
console.log(`✅ [${level.toUpperCase()}] ${message} | traceId: ${traceContext.traceId.substring(0, 8)}... | service: ${logEntry.service.name}`)
|
|
263
|
-
res.json({ success: true, message: 'Log enviado com sucesso' })
|
|
332
|
+
console.log(`✅ [${level.toUpperCase()}] ${message} | traceId: ${traceContext.traceId.substring(0, 8)}... | service: ${logEntry.service.name} | index: ${indexName}`)
|
|
333
|
+
res.json({ success: true, message: 'Log enviado com sucesso', index: indexName })
|
|
264
334
|
} catch (error) {
|
|
265
335
|
console.error('❌ Erro ao enviar log para OpenSearch:', error.message)
|
|
266
336
|
if (error.response) {
|
package/streams/bunyan.js
CHANGED
|
@@ -43,7 +43,7 @@ function createBunyanStream(options = {}) {
|
|
|
43
43
|
|
|
44
44
|
const meta = {
|
|
45
45
|
...cleanRecord,
|
|
46
|
-
service: { name: serviceName, version: record.service
|
|
46
|
+
service: { name: serviceName, version: (record.service && record.service.version) || '1.0.0' },
|
|
47
47
|
environment,
|
|
48
48
|
timestamp: new Date().toISOString(),
|
|
49
49
|
hostname: require('os').hostname(),
|
|
@@ -54,7 +54,7 @@ function createBunyanStream(options = {}) {
|
|
|
54
54
|
|
|
55
55
|
const payload = { level, message: record.msg || record.message || 'log', meta }
|
|
56
56
|
|
|
57
|
-
axios.post(`${loggerUrl}`, payload, { headers, timeout:
|
|
57
|
+
axios.post(`${loggerUrl}`, payload, { headers, timeout: 10000 }).catch(() => {})
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
}
|
package/streams/pino.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
let context, propagation, trace, W3CTraceContextPropagator
|
|
2
|
+
try {
|
|
3
|
+
const otelApi = require('@opentelemetry/api')
|
|
4
|
+
const otelCore = require('@opentelemetry/core')
|
|
5
|
+
context = otelApi.context
|
|
6
|
+
propagation = otelApi.propagation
|
|
7
|
+
trace = otelApi.trace
|
|
8
|
+
W3CTraceContextPropagator = otelCore.W3CTraceContextPropagator
|
|
9
|
+
} catch (_) {
|
|
10
|
+
context = { active: () => ({}) }
|
|
11
|
+
propagation = { setGlobalPropagator: () => {}, inject: () => {} }
|
|
12
|
+
trace = { getSpan: () => null }
|
|
13
|
+
W3CTraceContextPropagator = class {}
|
|
14
|
+
}
|
|
3
15
|
const axios = require('axios')
|
|
4
16
|
|
|
5
17
|
function createPinoStream(options = {}) {
|
|
@@ -23,7 +35,7 @@ function createPinoStream(options = {}) {
|
|
|
23
35
|
spanId = ctx.spanId
|
|
24
36
|
} else {
|
|
25
37
|
const span = trace.getSpan(context.active())
|
|
26
|
-
const spanContext = span
|
|
38
|
+
const spanContext = span && span.spanContext()
|
|
27
39
|
if (spanContext) {
|
|
28
40
|
traceId = spanContext.traceId
|
|
29
41
|
spanId = spanContext.spanId
|
|
@@ -44,7 +56,7 @@ function createPinoStream(options = {}) {
|
|
|
44
56
|
|
|
45
57
|
const meta = {
|
|
46
58
|
...record,
|
|
47
|
-
service: { name: serviceName, version: record.service
|
|
59
|
+
service: { name: serviceName, version: (record.service && record.service.version) || '1.0.0' },
|
|
48
60
|
environment,
|
|
49
61
|
timestamp: new Date().toISOString(),
|
|
50
62
|
hostname: require('os').hostname(),
|
|
@@ -54,7 +66,7 @@ function createPinoStream(options = {}) {
|
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
const payload = { level, message: record.msg || record.message || 'log', meta }
|
|
57
|
-
axios.post(`${loggerUrl}`, payload, { headers, timeout:
|
|
69
|
+
axios.post(`${loggerUrl}`, payload, { headers, timeout: 10000 }).catch(() => {})
|
|
58
70
|
}
|
|
59
71
|
}
|
|
60
72
|
}
|