@onlineapps/service-wrapper 2.1.3 → 2.1.5
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/package.json +1 -1
- package/src/HttpLoggingMiddleware.js +295 -0
- package/src/ServiceWrapper.js +10 -16
- package/src/index.js +3 -1
package/package.json
CHANGED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP Logging Middleware
|
|
5
|
+
*
|
|
6
|
+
* Configurable request/response logging for business services.
|
|
7
|
+
* Logs to both local files and central monitoring via wrapper.logger.
|
|
8
|
+
*
|
|
9
|
+
* Configuration in conn-config/config.json:
|
|
10
|
+
* {
|
|
11
|
+
* "wrapper": {
|
|
12
|
+
* "logging": {
|
|
13
|
+
* "enabled": true, // Master switch
|
|
14
|
+
* "level": "info", // Log level: debug, info, warn, error
|
|
15
|
+
* "requests": {
|
|
16
|
+
* "enabled": true, // Log incoming requests
|
|
17
|
+
* "bodies": true, // Include request bodies
|
|
18
|
+
* "headers": false // Include request headers (careful with auth tokens)
|
|
19
|
+
* },
|
|
20
|
+
* "responses": {
|
|
21
|
+
* "enabled": true, // Log outgoing responses
|
|
22
|
+
* "bodies": true // Include response bodies
|
|
23
|
+
* },
|
|
24
|
+
* "excludePaths": [ // Paths to exclude from logging
|
|
25
|
+
* "/health",
|
|
26
|
+
* "/status",
|
|
27
|
+
* "/metrics"
|
|
28
|
+
* ]
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const DEFAULT_CONFIG = {
|
|
35
|
+
enabled: true,
|
|
36
|
+
level: 'info',
|
|
37
|
+
requests: {
|
|
38
|
+
enabled: true,
|
|
39
|
+
bodies: true,
|
|
40
|
+
headers: false
|
|
41
|
+
},
|
|
42
|
+
responses: {
|
|
43
|
+
enabled: true,
|
|
44
|
+
bodies: true
|
|
45
|
+
},
|
|
46
|
+
excludePaths: ['/health', '/status', '/metrics', '/specification']
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Merge user config with defaults
|
|
51
|
+
*/
|
|
52
|
+
function mergeConfig(userConfig = {}) {
|
|
53
|
+
return {
|
|
54
|
+
enabled: userConfig.enabled ?? DEFAULT_CONFIG.enabled,
|
|
55
|
+
level: userConfig.level ?? DEFAULT_CONFIG.level,
|
|
56
|
+
requests: {
|
|
57
|
+
enabled: userConfig.requests?.enabled ?? DEFAULT_CONFIG.requests.enabled,
|
|
58
|
+
bodies: userConfig.requests?.bodies ?? DEFAULT_CONFIG.requests.bodies,
|
|
59
|
+
headers: userConfig.requests?.headers ?? DEFAULT_CONFIG.requests.headers
|
|
60
|
+
},
|
|
61
|
+
responses: {
|
|
62
|
+
enabled: userConfig.responses?.enabled ?? DEFAULT_CONFIG.responses.enabled,
|
|
63
|
+
bodies: userConfig.responses?.bodies ?? DEFAULT_CONFIG.responses.bodies
|
|
64
|
+
},
|
|
65
|
+
excludePaths: userConfig.excludePaths ?? DEFAULT_CONFIG.excludePaths
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if path should be excluded from logging
|
|
71
|
+
*/
|
|
72
|
+
function shouldExclude(path, excludePaths) {
|
|
73
|
+
return excludePaths.some(excluded => {
|
|
74
|
+
// Exact match or prefix match
|
|
75
|
+
return path === excluded || path.startsWith(excluded + '/');
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Safely stringify body for logging
|
|
81
|
+
*/
|
|
82
|
+
function safeStringify(body) {
|
|
83
|
+
if (body === undefined || body === null) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof body === 'string') {
|
|
88
|
+
// Already a string - could be JSON or plain text
|
|
89
|
+
try {
|
|
90
|
+
// Try to parse and re-stringify for consistent format
|
|
91
|
+
return JSON.parse(body);
|
|
92
|
+
} catch {
|
|
93
|
+
return body;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (Buffer.isBuffer(body)) {
|
|
98
|
+
return `[Buffer: ${body.length} bytes]`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Object - return as is (will be serialized by logger)
|
|
102
|
+
return body;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create request logging middleware
|
|
107
|
+
* @param {Function} getLogger - Function that returns logger instance
|
|
108
|
+
* @param {Object} config - Logging configuration
|
|
109
|
+
* @returns {Function} Express middleware
|
|
110
|
+
*/
|
|
111
|
+
function createRequestLogger(getLogger, config) {
|
|
112
|
+
const cfg = mergeConfig(config);
|
|
113
|
+
|
|
114
|
+
return (req, res, next) => {
|
|
115
|
+
// Skip if disabled
|
|
116
|
+
if (!cfg.enabled || !cfg.requests.enabled) {
|
|
117
|
+
return next();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Skip excluded paths
|
|
121
|
+
if (shouldExclude(req.path, cfg.excludePaths)) {
|
|
122
|
+
return next();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const logger = getLogger();
|
|
126
|
+
if (!logger) {
|
|
127
|
+
return next();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Build log data
|
|
131
|
+
const logData = {
|
|
132
|
+
type: 'request',
|
|
133
|
+
method: req.method,
|
|
134
|
+
path: req.path,
|
|
135
|
+
url: req.originalUrl,
|
|
136
|
+
ip: req.ip || req.connection?.remoteAddress,
|
|
137
|
+
userAgent: req.get('user-agent')
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Add query params if present
|
|
141
|
+
if (req.query && Object.keys(req.query).length > 0) {
|
|
142
|
+
logData.query = req.query;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Add request body if enabled
|
|
146
|
+
if (cfg.requests.bodies && req.body && Object.keys(req.body).length > 0) {
|
|
147
|
+
logData.body = safeStringify(req.body);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add headers if enabled
|
|
151
|
+
if (cfg.requests.headers) {
|
|
152
|
+
// Filter out sensitive headers
|
|
153
|
+
const headers = { ...req.headers };
|
|
154
|
+
delete headers.authorization;
|
|
155
|
+
delete headers.cookie;
|
|
156
|
+
logData.headers = headers;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Store start time for response duration
|
|
160
|
+
req._loggingStartTime = Date.now();
|
|
161
|
+
|
|
162
|
+
logger.info('HTTP Request', logData);
|
|
163
|
+
next();
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create response logging middleware
|
|
169
|
+
* @param {Function} getLogger - Function that returns logger instance
|
|
170
|
+
* @param {Object} config - Logging configuration
|
|
171
|
+
* @returns {Function} Express middleware
|
|
172
|
+
*/
|
|
173
|
+
function createResponseLogger(getLogger, config) {
|
|
174
|
+
const cfg = mergeConfig(config);
|
|
175
|
+
|
|
176
|
+
return (req, res, next) => {
|
|
177
|
+
// Skip if disabled
|
|
178
|
+
if (!cfg.enabled || !cfg.responses.enabled) {
|
|
179
|
+
return next();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Skip excluded paths
|
|
183
|
+
if (shouldExclude(req.path, cfg.excludePaths)) {
|
|
184
|
+
return next();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const logger = getLogger();
|
|
188
|
+
if (!logger) {
|
|
189
|
+
return next();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Store original methods
|
|
193
|
+
const originalSend = res.send;
|
|
194
|
+
const originalJson = res.json;
|
|
195
|
+
|
|
196
|
+
// Flag to prevent double logging
|
|
197
|
+
let logged = false;
|
|
198
|
+
|
|
199
|
+
const logResponse = (body) => {
|
|
200
|
+
if (logged) return;
|
|
201
|
+
logged = true;
|
|
202
|
+
|
|
203
|
+
const duration = req._loggingStartTime
|
|
204
|
+
? Date.now() - req._loggingStartTime
|
|
205
|
+
: null;
|
|
206
|
+
|
|
207
|
+
const logData = {
|
|
208
|
+
type: 'response',
|
|
209
|
+
method: req.method,
|
|
210
|
+
path: req.path,
|
|
211
|
+
statusCode: res.statusCode,
|
|
212
|
+
statusMessage: res.statusMessage
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (duration !== null) {
|
|
216
|
+
logData.durationMs = duration;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Add response body if enabled
|
|
220
|
+
if (cfg.responses.bodies && body !== undefined) {
|
|
221
|
+
logData.body = safeStringify(body);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Choose log level based on status code
|
|
225
|
+
if (res.statusCode >= 500) {
|
|
226
|
+
logger.error('HTTP Response', logData);
|
|
227
|
+
} else if (res.statusCode >= 400) {
|
|
228
|
+
logger.warn('HTTP Response', logData);
|
|
229
|
+
} else {
|
|
230
|
+
logger.info('HTTP Response', logData);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Override res.send
|
|
235
|
+
res.send = function(body) {
|
|
236
|
+
logResponse(body);
|
|
237
|
+
return originalSend.call(this, body);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Override res.json
|
|
241
|
+
res.json = function(body) {
|
|
242
|
+
logResponse(body);
|
|
243
|
+
return originalJson.call(this, body);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
next();
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Create combined HTTP logging middleware
|
|
252
|
+
* @param {Function} getLogger - Function that returns logger instance
|
|
253
|
+
* @param {Object} config - Logging configuration from wrapper.logging
|
|
254
|
+
* @returns {Object} Object with requestLogger and responseLogger middlewares
|
|
255
|
+
*/
|
|
256
|
+
function createHttpLogging(getLogger, config = {}) {
|
|
257
|
+
const cfg = mergeConfig(config);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
config: cfg,
|
|
261
|
+
requestLogger: createRequestLogger(getLogger, config),
|
|
262
|
+
responseLogger: createResponseLogger(getLogger, config),
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Apply both middlewares to Express app
|
|
266
|
+
* @param {Object} app - Express application
|
|
267
|
+
*/
|
|
268
|
+
applyTo(app) {
|
|
269
|
+
if (!cfg.enabled) {
|
|
270
|
+
console.log('[HttpLogging] Disabled by configuration');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (cfg.requests.enabled) {
|
|
275
|
+
app.use(this.requestLogger);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (cfg.responses.enabled) {
|
|
279
|
+
app.use(this.responseLogger);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log(`[HttpLogging] Enabled (requests: ${cfg.requests.enabled}, responses: ${cfg.responses.enabled}, bodies: ${cfg.requests.bodies})`);
|
|
283
|
+
console.log(`[HttpLogging] Excluded paths: ${cfg.excludePaths.join(', ')}`);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
module.exports = {
|
|
289
|
+
createHttpLogging,
|
|
290
|
+
createRequestLogger,
|
|
291
|
+
createResponseLogger,
|
|
292
|
+
mergeConfig,
|
|
293
|
+
DEFAULT_CONFIG
|
|
294
|
+
};
|
|
295
|
+
|
package/src/ServiceWrapper.js
CHANGED
|
@@ -22,6 +22,7 @@ const CookbookConnector = require('@onlineapps/conn-orch-cookbook');
|
|
|
22
22
|
const CacheConnector = require('@onlineapps/conn-base-cache');
|
|
23
23
|
const ErrorHandlerConnector = require('@onlineapps/conn-infra-error-handler');
|
|
24
24
|
const { ValidationOrchestrator } = require('@onlineapps/conn-orch-validator');
|
|
25
|
+
const { createHttpLogging } = require('./HttpLoggingMiddleware');
|
|
25
26
|
|
|
26
27
|
const INFRA_QUEUE_OWNERS = {
|
|
27
28
|
'workflow.init': 'Gateway (api_gateway)',
|
|
@@ -558,23 +559,16 @@ class ServiceWrapper {
|
|
|
558
559
|
// Logger is now available, use it
|
|
559
560
|
this.logger.info('Monitoring connector initialized', { logsDir });
|
|
560
561
|
|
|
561
|
-
//
|
|
562
|
+
// Apply HTTP logging middleware (configurable via wrapper.logging)
|
|
562
563
|
if (this.app) {
|
|
563
|
-
this.
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
statusCode: res.statusCode,
|
|
572
|
-
duration
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
next();
|
|
577
|
-
});
|
|
564
|
+
const loggingConfig = this.config.wrapper?.logging || {};
|
|
565
|
+
const httpLogging = createHttpLogging(() => this.logger, loggingConfig);
|
|
566
|
+
|
|
567
|
+
// Store for access from outside if needed
|
|
568
|
+
this.httpLogging = httpLogging;
|
|
569
|
+
|
|
570
|
+
// Apply middlewares to Express app
|
|
571
|
+
httpLogging.applyTo(this.app);
|
|
578
572
|
}
|
|
579
573
|
}
|
|
580
574
|
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const ServiceWrapper = require('./ServiceWrapper');
|
|
15
15
|
const { ConfigLoader } = require('./ConfigLoader');
|
|
16
|
+
const HttpLoggingMiddleware = require('./HttpLoggingMiddleware');
|
|
16
17
|
|
|
17
18
|
// Note: WorkflowProcessor and ApiCaller functionality has been moved to connectors:
|
|
18
19
|
// - WorkflowProcessor -> @onlineapps/conn-orch-orchestrator
|
|
@@ -21,5 +22,6 @@ const { ConfigLoader } = require('./ConfigLoader');
|
|
|
21
22
|
module.exports = ServiceWrapper;
|
|
22
23
|
module.exports.ServiceWrapper = ServiceWrapper;
|
|
23
24
|
module.exports.ConfigLoader = ConfigLoader;
|
|
25
|
+
module.exports.HttpLoggingMiddleware = HttpLoggingMiddleware;
|
|
24
26
|
module.exports.default = ServiceWrapper;
|
|
25
|
-
module.exports.VERSION = '2.1.
|
|
27
|
+
module.exports.VERSION = '2.1.4';
|