azify-logger 1.0.20 → 1.0.23
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/middleware-express.js +11 -2
- package/middleware-restify.js +14 -2
- package/package.json +2 -5
- package/server.js +54 -81
package/middleware-express.js
CHANGED
|
@@ -382,8 +382,17 @@ function createExpressLoggingMiddleware(options = {}) {
|
|
|
382
382
|
|
|
383
383
|
const statusCode = res._actualStatusCode || res._statusCode || res.statusCode || 200
|
|
384
384
|
|
|
385
|
+
let parsedResponseBody = serializedResponseBody
|
|
386
|
+
try {
|
|
387
|
+
if (typeof serializedResponseBody === 'string' && serializedResponseBody.trim().startsWith('{')) {
|
|
388
|
+
parsedResponseBody = JSON.parse(serializedResponseBody)
|
|
389
|
+
} else if (typeof serializedResponseBody === 'string' && serializedResponseBody.trim().startsWith('[')) {
|
|
390
|
+
parsedResponseBody = JSON.parse(serializedResponseBody)
|
|
391
|
+
}
|
|
392
|
+
} catch (_) {}
|
|
393
|
+
|
|
385
394
|
const responseMessage = serializedResponseBody && serializedResponseBody.length > 0
|
|
386
|
-
? `[RESPONSE] ${
|
|
395
|
+
? `[RESPONSE] ${req.method} ${req.url} ${statusCode}`
|
|
387
396
|
: `[RESPONSE] ${req.method} ${req.url} ${statusCode} ${duration}ms`
|
|
388
397
|
|
|
389
398
|
const responseData = {
|
|
@@ -392,7 +401,7 @@ function createExpressLoggingMiddleware(options = {}) {
|
|
|
392
401
|
statusCode: statusCode,
|
|
393
402
|
responseTime: duration,
|
|
394
403
|
responseHeaders: res.getHeaders ? res.getHeaders() : {},
|
|
395
|
-
responseBody:
|
|
404
|
+
responseBody: parsedResponseBody // Use parsed object instead of string
|
|
396
405
|
}
|
|
397
406
|
|
|
398
407
|
try { res._azifyResponseLogged = true } catch (_) {}
|
package/middleware-restify.js
CHANGED
|
@@ -282,8 +282,20 @@ function createRestifyLoggingMiddleware(options = {}) {
|
|
|
282
282
|
|
|
283
283
|
const statusCode = res._actualStatusCode || res._statusCode || res.statusCode || 200
|
|
284
284
|
|
|
285
|
+
// Try to parse serializedResponseBody back to object for cleaner display in Grafana
|
|
286
|
+
let parsedResponseBody = serializedResponseBody
|
|
287
|
+
try {
|
|
288
|
+
if (typeof serializedResponseBody === 'string' && serializedResponseBody.trim().startsWith('{')) {
|
|
289
|
+
parsedResponseBody = JSON.parse(serializedResponseBody)
|
|
290
|
+
} else if (typeof serializedResponseBody === 'string' && serializedResponseBody.trim().startsWith('[')) {
|
|
291
|
+
parsedResponseBody = JSON.parse(serializedResponseBody)
|
|
292
|
+
}
|
|
293
|
+
} catch (_) {
|
|
294
|
+
// Keep as string if parsing fails
|
|
295
|
+
}
|
|
296
|
+
|
|
285
297
|
const responseMessage = serializedResponseBody && serializedResponseBody.length > 0
|
|
286
|
-
? `[RESPONSE] ${
|
|
298
|
+
? `[RESPONSE] ${req.method} ${req.url} ${statusCode}`
|
|
287
299
|
: `[RESPONSE] ${req.method} ${req.url} ${statusCode} ${duration}ms`
|
|
288
300
|
|
|
289
301
|
const responseData = {
|
|
@@ -292,7 +304,7 @@ function createRestifyLoggingMiddleware(options = {}) {
|
|
|
292
304
|
statusCode: statusCode,
|
|
293
305
|
responseTime: duration,
|
|
294
306
|
responseHeaders: res.getHeaders ? res.getHeaders() : {},
|
|
295
|
-
responseBody:
|
|
307
|
+
responseBody: parsedResponseBody // Use parsed object instead of string
|
|
296
308
|
}
|
|
297
309
|
|
|
298
310
|
try { res._azifyResponseLogged = true } catch (_) {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azify-logger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "Azify Logger Client - Centralized logging for OpenSearch",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -30,14 +30,11 @@
|
|
|
30
30
|
"axios": "^1.6.0",
|
|
31
31
|
"cors": "^2.8.5",
|
|
32
32
|
"express": "^4.18.2",
|
|
33
|
-
"express-session": "^1.17.3",
|
|
34
|
-
"passport": "^0.6.0",
|
|
35
|
-
"passport-azure-ad": "^4.3.5",
|
|
36
33
|
"require-in-the-middle": "^7.4.0",
|
|
37
34
|
"uuid": "^9.0.1"
|
|
38
35
|
},
|
|
39
36
|
"optionalDependencies": {
|
|
40
|
-
"@opentelemetry/api": "^1.
|
|
37
|
+
"@opentelemetry/api": "^1.9.0",
|
|
41
38
|
"@opentelemetry/auto-instrumentations-node": "^0.40.0",
|
|
42
39
|
"@opentelemetry/core": "^1.28.0",
|
|
43
40
|
"@opentelemetry/exporter-trace-otlp-http": "^0.45.0",
|
package/server.js
CHANGED
|
@@ -27,19 +27,57 @@ app.use(express.json({ limit: '10mb' }))
|
|
|
27
27
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }))
|
|
28
28
|
app.use(cors())
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
let ensureAuthenticated = (req, res, next) => next()
|
|
30
|
+
const IS_LOCAL = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev' || !process.env.NODE_ENV
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
function isPrivateOrLocalhost(ip) {
|
|
33
|
+
if (IS_LOCAL) {
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ip = ip.replace('::ffff:', '').replace('::1', '127.0.0.1')
|
|
38
|
+
|
|
39
|
+
if (ip === '127.0.0.1' || ip === 'localhost' || ip === '::1') {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parts = ip.split('.').map(Number)
|
|
44
|
+
|
|
45
|
+
if (parts.length !== 4) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (parts[0] === 10) return true
|
|
50
|
+
if (parts[0] === 127) return true
|
|
51
|
+
if (parts[0] === 192 && parts[1] === 168) return true
|
|
52
|
+
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true
|
|
53
|
+
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateNetworkAccess(req, res, next) {
|
|
58
|
+
if (req.path === '/health' || req.path === '/') {
|
|
59
|
+
return next()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const clientIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
|
63
|
+
req.headers['x-real-ip'] ||
|
|
64
|
+
req.ip ||
|
|
65
|
+
req.connection.remoteAddress ||
|
|
66
|
+
req.socket.remoteAddress
|
|
67
|
+
|
|
68
|
+
if (!IS_LOCAL && !isPrivateOrLocalhost(clientIP)) {
|
|
69
|
+
return res.status(403).json({
|
|
70
|
+
success: false,
|
|
71
|
+
message: 'Forbidden. Access denied - endpoint only accessible from same server (localhost/private IPs).',
|
|
72
|
+
clientIP: clientIP
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
next()
|
|
41
77
|
}
|
|
42
78
|
|
|
79
|
+
app.use(validateNetworkAccess)
|
|
80
|
+
|
|
43
81
|
const tracer = trace.getTracer('azify-logger', '1.0.0')
|
|
44
82
|
const propagator = new W3CTraceContextPropagator()
|
|
45
83
|
|
|
@@ -66,7 +104,7 @@ async function ensureIndexTemplate() {
|
|
|
66
104
|
},
|
|
67
105
|
mappings: {
|
|
68
106
|
properties: {
|
|
69
|
-
timestamp: { type: 'date' },
|
|
107
|
+
'@timestamp': { type: 'date' },
|
|
70
108
|
level: { type: 'keyword' },
|
|
71
109
|
message: { type: 'text' },
|
|
72
110
|
service: {
|
|
@@ -114,54 +152,6 @@ async function ensureIndexTemplate() {
|
|
|
114
152
|
|
|
115
153
|
ensureIndexTemplate()
|
|
116
154
|
|
|
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
|
-
}
|
|
164
|
-
|
|
165
155
|
function generateTraceId() {
|
|
166
156
|
return Math.random().toString(36).substring(2, 15) +
|
|
167
157
|
Math.random().toString(36).substring(2, 15)
|
|
@@ -194,41 +184,24 @@ function getOrCreateTraceContext(requestId) {
|
|
|
194
184
|
app.get('/health', (req, res) => {
|
|
195
185
|
res.json({
|
|
196
186
|
status: 'ok',
|
|
197
|
-
service: 'azify-logger'
|
|
198
|
-
authEnabled: authEnabled
|
|
187
|
+
service: 'azify-logger'
|
|
199
188
|
})
|
|
200
189
|
})
|
|
201
190
|
|
|
202
|
-
app.get('/',
|
|
191
|
+
app.get('/', (req, res) => {
|
|
203
192
|
res.json({
|
|
204
193
|
service: 'azify-logger',
|
|
205
194
|
version: '1.0.0',
|
|
206
195
|
endpoints: {
|
|
207
196
|
health: '/health',
|
|
208
|
-
testLog: '/test-log'
|
|
209
|
-
|
|
210
|
-
login: '/auth/login',
|
|
211
|
-
logout: '/auth/logout',
|
|
212
|
-
me: '/auth/me'
|
|
213
|
-
} : null
|
|
214
|
-
},
|
|
215
|
-
user: req.user || null
|
|
197
|
+
testLog: '/test-log'
|
|
198
|
+
}
|
|
216
199
|
})
|
|
217
200
|
})
|
|
218
201
|
|
|
219
202
|
app.post('/log', async (req, res) => {
|
|
220
203
|
let { level, message, meta } = req.body
|
|
221
204
|
|
|
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
|
-
|
|
232
205
|
if (!level || !message) {
|
|
233
206
|
return res.status(400).json({ success: false, message: 'Level and message are required.' })
|
|
234
207
|
}
|
|
@@ -297,7 +270,7 @@ app.post('/log', async (req, res) => {
|
|
|
297
270
|
}
|
|
298
271
|
|
|
299
272
|
const logEntry = {
|
|
300
|
-
timestamp: (meta && meta.timestamp) || new Date().toISOString(),
|
|
273
|
+
'@timestamp': (meta && meta.timestamp) || new Date().toISOString(),
|
|
301
274
|
level,
|
|
302
275
|
message,
|
|
303
276
|
service: {
|