@onlineapps/service-wrapper 2.1.5 → 2.1.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/service-wrapper",
3
- "version": "2.1.5",
3
+ "version": "2.1.10",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -22,7 +22,6 @@ 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');
26
25
 
27
26
  const INFRA_QUEUE_OWNERS = {
28
27
  'workflow.init': 'Gateway (api_gateway)',
@@ -274,6 +273,7 @@ class ServiceWrapper {
274
273
  * @private
275
274
  */
276
275
  _logPhase(phase, phaseName, status, error = null, duration = null) {
276
+ const serviceName = this.config.service?.name || 'unnamed-service';
277
277
  const logMsg = `[FÁZE ${phase}] ${phaseName} - ${status}`;
278
278
  const logData = {
279
279
  phase,
@@ -282,8 +282,19 @@ class ServiceWrapper {
282
282
  timestamp: new Date().toISOString(),
283
283
  duration,
284
284
  error: error ? { message: error.message, stack: error.stack } : null,
285
- serviceName: this.config.service?.name || 'unnamed-service'
285
+ serviceName
286
286
  };
287
+
288
+ // Structured log for service wrapper phases
289
+ console.log(`[${serviceName}:service-wrapper:init:PHASE_${status}] ${JSON.stringify({
290
+ timestamp: new Date().toISOString(),
291
+ service: serviceName,
292
+ phase,
293
+ phase_name: phaseName,
294
+ status,
295
+ duration_ms: duration,
296
+ error: error ? error.message : null
297
+ })}`);
287
298
 
288
299
  if (status === 'PASSED') {
289
300
  this.logger?.info(logMsg, logData);
@@ -559,17 +570,9 @@ class ServiceWrapper {
559
570
  // Logger is now available, use it
560
571
  this.logger.info('Monitoring connector initialized', { logsDir });
561
572
 
562
- // Apply HTTP logging middleware (configurable via wrapper.logging)
563
- if (this.app) {
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);
572
- }
573
+ // NOTE: HTTP logging middleware is applied directly in service's app.js
574
+ // using middlewares/httpLogging.js with lazy logger access via lib/logger.js
575
+ // Configuration is in conn-config/config.json -> wrapper.logging (for reference)
573
576
  }
574
577
 
575
578
  /**
@@ -1264,10 +1267,13 @@ class ServiceWrapper {
1264
1267
  }
1265
1268
 
1266
1269
  // Normalize message format: Gateway sends workflowId, orchestrator expects workflow_id
1270
+ // V2 format uses step_id, V1 used id - support both for backwards compatibility during migration
1271
+ const firstStep = message.cookbook?.steps?.[0];
1272
+ const firstStepId = firstStep?.step_id || firstStep?.id;
1267
1273
  const normalizedMessage = {
1268
1274
  ...message,
1269
1275
  workflow_id: message.workflow_id || message.workflowId,
1270
- current_step: message.current_step || (message.cookbook?.steps?.[0]?.id)
1276
+ current_step: message.current_step || firstStepId
1271
1277
  };
1272
1278
 
1273
1279
  // Validate normalized message has required fields
package/src/index.js CHANGED
@@ -11,17 +11,109 @@
11
11
  * @since 2.0.0
12
12
  */
13
13
 
14
+ const path = require('path');
14
15
  const ServiceWrapper = require('./ServiceWrapper');
15
16
  const { ConfigLoader } = require('./ConfigLoader');
16
- const HttpLoggingMiddleware = require('./HttpLoggingMiddleware');
17
17
 
18
18
  // Note: WorkflowProcessor and ApiCaller functionality has been moved to connectors:
19
19
  // - WorkflowProcessor -> @onlineapps/conn-orch-orchestrator
20
20
  // - ApiCaller -> @onlineapps/conn-orch-api-mapper
21
21
 
22
+ // Note: HTTP logging middleware is now in each service's middlewares/httpLogging.js
23
+ // It uses lazy logger access via lib/logger.js (set after wrapper.initialize())
24
+
25
+ /**
26
+ * Bootstrap a business service with standard configuration
27
+ * Eliminates boilerplate code in service index.js
28
+ *
29
+ * @param {string} serviceRoot - Service root directory (__dirname from index.js)
30
+ * @param {Object} [options] - Optional overrides
31
+ * @param {Object} [options.app] - Express app (default: require('./src/app'))
32
+ * @param {Object} [options.config] - Config (default: require('./src/config'))
33
+ * @param {Function} [options.setLogger] - Logger setter (default: require('./src/lib/logger').setLogger)
34
+ * @returns {Promise<{wrapper: ServiceWrapper, server: Object}>}
35
+ *
36
+ * @example
37
+ * // index.js (minimal)
38
+ * require('@onlineapps/service-wrapper').bootstrap(__dirname);
39
+ */
40
+ async function bootstrap(serviceRoot, options = {}) {
41
+ // Load dotenv first
42
+ require('dotenv').config({ path: path.join(serviceRoot, '.env') });
43
+
44
+ // Load app, config, and logger from service
45
+ const app = options.app || require(path.join(serviceRoot, 'src', 'app'));
46
+ const config = options.config || require(path.join(serviceRoot, 'src', 'config'));
47
+
48
+ // Logger setter is optional - some services may not have it
49
+ let setLogger = options.setLogger;
50
+ if (!setLogger) {
51
+ try {
52
+ const loggerModule = require(path.join(serviceRoot, 'src', 'lib', 'logger'));
53
+ setLogger = loggerModule.setLogger;
54
+ } catch (e) {
55
+ // No logger module - that's OK
56
+ setLogger = () => {};
57
+ }
58
+ }
59
+
60
+ console.log(`Starting ${config.service.name} v${config.service.version}...`);
61
+
62
+ // 1. Start HTTP server
63
+ const PORT = config.service.port;
64
+ const server = app.listen(PORT, () => {
65
+ console.log(`✓ HTTP server listening on port ${PORT}`);
66
+ console.log(`✓ Health check: http://localhost:${PORT}/health`);
67
+ });
68
+
69
+ // 2. Initialize Service Wrapper (MQ, Registry, Monitoring, etc.)
70
+ const wrapper = new ServiceWrapper({
71
+ app,
72
+ server,
73
+ serviceRoot,
74
+ config: {
75
+ service: {
76
+ name: config.service.name,
77
+ version: config.service.version,
78
+ port: config.service.port,
79
+ url: process.env.SERVICE_URL || `http://localhost:${PORT}`
80
+ },
81
+ wrapper: config.wrapper
82
+ },
83
+ operations: config.operations,
84
+ validationProof: config.validationProof
85
+ });
86
+
87
+ await wrapper.initialize();
88
+
89
+ // Initialize centralized logger for use throughout the service
90
+ if (setLogger && wrapper.logger) {
91
+ setLogger(wrapper.logger);
92
+ }
93
+
94
+ console.log(`✓ Service Wrapper initialized (MQ, Registry, Monitoring)`);
95
+ console.log(`✓ Local logs: logs/app.{date}.log`);
96
+ console.log(`✓ ${config.service.name} ready\n`);
97
+
98
+ // 3. Setup graceful shutdown
99
+ const shutdown = async () => {
100
+ console.log('\nShutting down gracefully...');
101
+ await wrapper.shutdown();
102
+ server.close(() => {
103
+ console.log('Server closed');
104
+ process.exit(0);
105
+ });
106
+ };
107
+
108
+ process.on('SIGTERM', shutdown);
109
+ process.on('SIGINT', shutdown);
110
+
111
+ return { wrapper, server };
112
+ }
113
+
22
114
  module.exports = ServiceWrapper;
23
115
  module.exports.ServiceWrapper = ServiceWrapper;
24
116
  module.exports.ConfigLoader = ConfigLoader;
25
- module.exports.HttpLoggingMiddleware = HttpLoggingMiddleware;
117
+ module.exports.bootstrap = bootstrap;
26
118
  module.exports.default = ServiceWrapper;
27
- module.exports.VERSION = '2.1.4';
119
+ module.exports.VERSION = '2.1.10';
@@ -1,295 +0,0 @@
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
-