whatap 1.0.3 → 1.0.4

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.
@@ -4,7 +4,7 @@
4
4
  * can be found in the LICENSE file.
5
5
  */
6
6
 
7
- var WHATAP_CONF = process.env.WHATAP_CONF || 'whatap';
7
+ var WHATAP_CONF = process.env.WHATAP_CONF || 'whatap.conf';
8
8
 
9
9
  var fs = require('fs'),
10
10
  path = require('path'),
@@ -154,7 +154,7 @@ var Configuration = (function() {
154
154
  if(err) {
155
155
  if(applyEmptyConf==false){
156
156
  applyEmptyConf=true;
157
- require('../logger').print("WHATAP-301", "No Configure file '"+propertyFile+"'", true);
157
+ require('../logger').print("WHATAP-301", "No Configure file '"+propertyFile+"'", false);
158
158
 
159
159
  var defKeys = Object.keys(ConfigDefault);
160
160
  var p = {};
@@ -253,7 +253,7 @@ var Configuration = (function() {
253
253
  confDir = this.getProperty('app.root', process.cwd());
254
254
  }
255
255
 
256
- var default_conf = WHATAP_CONF+".conf";
256
+ var default_conf = WHATAP_CONF;
257
257
  var configFile = this.getProperty('whatap.config', default_conf);
258
258
 
259
259
  var confFullPathFile = path.join(confDir, configFile);
@@ -7,24 +7,78 @@
7
7
  var fs = require('fs'),
8
8
  path = require('path');
9
9
 
10
- var TraceContextManager = require('../trace/trace-context-manager'),
11
- HashUtil = require('../util/hashutil'),
12
- ArrayUtil = require('../util/array-util'),
13
- conf = require('../conf/configure'),
10
+ var conf = require('../conf/configure'),
14
11
  Logger = require('../logger');
15
12
 
16
13
  var loadedPackageList = [],
17
- loadedPackages = {},
18
- dependencies = [];
14
+ loadedPackages = {};
19
15
 
20
16
  var PackageCtrHelper = function() {};
17
+
18
+ /**
19
+ * Check if a module is ESM by checking file extension or package.json
20
+ */
21
+ PackageCtrHelper.isESModule = function(modulePath) {
22
+ if (modulePath.endsWith('.mjs')) {
23
+ return true;
24
+ }
25
+
26
+ // Check if the module's package.json has "type": "module"
27
+ try {
28
+ var moduleDir = path.dirname(modulePath);
29
+ var checkDir = moduleDir;
30
+
31
+ // Walk up to find package.json
32
+ for (var i = 0; i < 10; i++) {
33
+ try {
34
+ var pkgPath = path.join(checkDir, 'package.json');
35
+ if (fs.existsSync(pkgPath)) {
36
+ var pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
37
+ if (pkg.type === 'module') {
38
+ return true;
39
+ }
40
+ break;
41
+ }
42
+ var parentDir = path.dirname(checkDir);
43
+ if (parentDir === checkDir) break;
44
+ checkDir = parentDir;
45
+ } catch(e) {
46
+ break;
47
+ }
48
+ }
49
+ } catch(e) {
50
+ // Ignore errors
51
+ }
52
+
53
+ return false;
54
+ };
55
+
56
+ /**
57
+ * Load a package using require (CommonJS) or dynamic import (ESM)
58
+ * Returns a Promise for ESM modules
59
+ */
21
60
  PackageCtrHelper.addPackage = function(realPath){
22
61
  try {
23
62
  if(!loadedPackages[realPath]){
24
- loadedPackages[realPath] = require(realPath);
63
+ if (PackageCtrHelper.isESModule(realPath)) {
64
+ // ESM module - return a promise
65
+ return import(realPath).then(function(module) {
66
+ loadedPackages[realPath] = module;
67
+ return module;
68
+ }).catch(function(e) {
69
+ Logger.printError("WHATAP-201", "PackageControler helper (ESM)", e, false);
70
+ return null;
71
+ });
72
+ } else {
73
+ // CommonJS module - synchronous
74
+ loadedPackages[realPath] = require(realPath);
75
+ return Promise.resolve(loadedPackages[realPath]);
76
+ }
25
77
  }
78
+ return Promise.resolve(loadedPackages[realPath]);
26
79
  } catch(e) {
27
- return Logger.printError("WHATAP-120", "PackageControler helper", e, false);
80
+ Logger.printError("WHATAP-202", "PackageControler helper", e, false);
81
+ return Promise.resolve(null);
28
82
  }
29
83
  };
30
84
  PackageCtrHelper.getLoadedPackageList = function(filter){
@@ -51,7 +105,7 @@ PackageCtrHelper.getInstalledPackageList = function(){
51
105
  }
52
106
  return dirs;
53
107
  } catch(e) {
54
- Logger.printError('WHATAP-121', 'Get Installed Package List ', e, true);
108
+ Logger.printError('WHATAP-203', 'Get Installed Package List ', e, false);
55
109
  }
56
110
 
57
111
  return [];
@@ -65,31 +119,372 @@ PackageCtrHelper.getPackageDetail = function(pkg){
65
119
  packageJson = JSON.parse(packageFile);
66
120
  return packageJson.dependencies;
67
121
  } catch(e) {
68
- Logger.printError('WHATAP-122', 'Get Package Detail ', e, true);
122
+ Logger.printError('WHATAP-204', 'Get Package Detail ', e, false);
123
+ }
124
+ };
125
+
126
+ /**
127
+ * Dynamically hook a method in a user-defined module
128
+ * @param {Object} moduleObj - The module object to hook
129
+ * @param {string} modulePath - Path to the module
130
+ * @param {string} methodName - Name of the method to hook
131
+ */
132
+ PackageCtrHelper.dynamicHook = function(moduleObj, modulePath, methodName) {
133
+ try {
134
+ if (!moduleObj) {
135
+ Logger.printError('WHATAP-205',
136
+ 'Module object is null or undefined for path: ' + modulePath,
137
+ null, false);
138
+ return;
139
+ }
140
+
141
+ // Get the target object and method
142
+ var targetObj = moduleObj;
143
+ var targetMethod = methodName;
144
+
145
+ // Check if method exists as a direct property
146
+ if (typeof targetObj[targetMethod] === 'function') {
147
+ PackageCtrHelper.hookMethod(targetObj, modulePath, targetMethod, null);
148
+ return;
149
+ }
150
+
151
+ // Check if it's a class with prototype methods
152
+ if (targetObj.prototype && typeof targetObj.prototype[targetMethod] === 'function') {
153
+ PackageCtrHelper.hookMethod(targetObj.prototype, modulePath, targetMethod, null);
154
+ return;
155
+ }
156
+
157
+ // Check if module exports is a class instance
158
+ if (targetObj.constructor && targetObj.constructor.prototype &&
159
+ typeof targetObj.constructor.prototype[targetMethod] === 'function') {
160
+ PackageCtrHelper.hookMethod(targetObj.constructor.prototype, modulePath, targetMethod, null);
161
+ return;
162
+ }
163
+
164
+ Logger.printError('WHATAP-206',
165
+ 'Method "' + targetMethod + '" not found in module ' + modulePath,
166
+ null, false);
167
+ } catch (e) {
168
+ Logger.printError('WHATAP-207',
169
+ 'Error hooking ' + modulePath + '/' + methodName, e, false);
69
170
  }
70
171
  };
71
172
 
72
- conf.on('hook_method_patterns', function (value) {
73
- var methods = value.split(',');
173
+ /**
174
+ * Hook a specific method on an object
175
+ * @param {Object} obj - The object containing the method
176
+ * @param {string} modulePath - Path to the module (for logging)
177
+ * @param {string} methodName - Name of the method to hook
178
+ * @param {string} className - Optional class name for proper display
179
+ */
180
+ PackageCtrHelper.hookMethod = function(obj, modulePath, methodName, className) {
181
+ try {
182
+ var TraceContextManager = require('../trace/trace-context-manager');
183
+ // Lazy require to avoid circular dependency issues
184
+ var AsyncSender = require('../udp/async_sender');
185
+ var PacketTypeEnum = require('../udp/packet_type_enum');
186
+
187
+ var originalMethod = obj[methodName];
188
+ obj[methodName] = function() {
189
+ var ctx = TraceContextManager.getCurrentContext();
190
+ if (!ctx) {
191
+ return originalMethod.apply(this, arguments);
192
+ }
193
+
194
+ var startTime = Date.now();
195
+ var error = null;
196
+ var result = null;
197
+
198
+ // Capture original arguments once for later use
199
+ var originalArgs = arguments;
200
+
201
+ // Format: "ClassName.methodName" if className exists, otherwise "modulePath/methodName"
202
+ var methodFullPath = className ? className + '.' + methodName : modulePath + '/' + methodName;
203
+
204
+ // Convert arguments to string once
205
+ var argsString = '';
206
+ try {
207
+ var argsArray = Array.prototype.slice.call(originalArgs);
208
+ argsString = argsArray.map(function(arg) {
209
+ if (arg === null) return 'null';
210
+ if (arg === undefined) return 'undefined';
211
+ if (typeof arg === 'function') return '[Function]';
212
+ if (typeof arg === 'object') {
213
+ try {
214
+ return JSON.stringify(arg, null, 0);
215
+ } catch(e) {
216
+ return '[Object]';
217
+ }
218
+ }
219
+ return String(arg);
220
+ }).join(', ');
221
+
222
+ // Truncate if too long
223
+ if (argsString.length > 1000) {
224
+ argsString = argsString.substring(0, 1000) + '...';
225
+ }
226
+ } catch(e) {
227
+ argsString = '[args parsing error]';
228
+ }
229
+
230
+ var sendMetrics = function(actualElapsed) {
231
+ // Check if AsyncSender is available before use
232
+ if (AsyncSender && typeof AsyncSender.send_packet === 'function') {
233
+ var payloads = [methodFullPath, argsString];
234
+ ctx.elapsed = actualElapsed;
235
+
236
+ AsyncSender.send_packet(PacketTypeEnum.TX_METHOD, ctx, payloads);
237
+ }
238
+ };
239
+
240
+ try {
241
+ result = originalMethod.apply(this, originalArgs);
242
+
243
+ // Check if result is a Promise
244
+ if (result && typeof result.then === 'function') {
245
+ // Async function - wrap the promise to measure elapsed time
246
+ return result.then(function(value) {
247
+ var elapsed = Date.now() - startTime;
248
+ sendMetrics(elapsed);
249
+ return value;
250
+ }).catch(function(e) {
251
+ var elapsed = Date.now() - startTime;
252
+
253
+ if (!ctx.error) {
254
+ ctx.error = 1;
255
+ ctx.status = 500;
256
+ }
257
+
258
+ // Check if AsyncSender is available before use
259
+ if (AsyncSender && typeof AsyncSender.send_packet === 'function') {
260
+ var errors = [e.message || '', e.stack || ''];
261
+ AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
262
+ }
263
+
264
+ sendMetrics(elapsed);
265
+ throw e;
266
+ });
267
+ } else {
268
+ // Synchronous function
269
+ var elapsed = Date.now() - startTime;
270
+ sendMetrics(elapsed);
271
+ return result;
272
+ }
273
+ } catch (e) {
274
+ error = e;
275
+
276
+ if (!ctx.error) {
277
+ ctx.error = 1;
278
+ ctx.status = 500;
279
+ }
280
+
281
+ // Check if AsyncSender is available before use
282
+ if (AsyncSender && typeof AsyncSender.send_packet === 'function') {
283
+ var errors = [e.message || '', e.stack || ''];
284
+ AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
285
+ } else {
286
+ Logger.printError('WHATAP-208', 'AsyncSender.send_packet is not available', null, false);
287
+ }
288
+
289
+ var elapsed = Date.now() - startTime;
290
+ sendMetrics(elapsed);
291
+ throw e;
292
+ }
293
+ };
294
+
295
+ // Preserve original function metadata
296
+ obj[methodName].__whatapHooked__ = true;
297
+ obj[methodName].__whatapOriginal__ = originalMethod;
298
+ obj[methodName].__whatapModulePath__ = modulePath;
299
+ } catch (e) {
300
+ Logger.printError('WHATAP-209',
301
+ 'Error in hookMethod for ' + modulePath + '/' + methodName, e, false);
302
+ }
303
+ };
304
+
305
+ /**
306
+ * Initialize custom method hooks based on hook_method_patterns configuration
307
+ * This runs once when the module is first loaded
308
+ */
309
+ (function initializeMethodHooks() {
310
+ var hookPatterns = conf['hook_method_patterns'];
311
+
312
+ if (!hookPatterns || typeof hookPatterns !== 'string' || hookPatterns.trim() === '') {
313
+ return;
314
+ }
315
+
316
+ var methods = hookPatterns.split(',');
74
317
 
75
318
  methods.forEach(function (path) {
319
+ path = path.trim();
76
320
 
77
- var relative_path = path.substr(0, path.lastIndexOf('/'));
78
- var method = path.substr(path.lastIndexOf('/') + 1, path.length);
321
+ // Parse path: ./src/user/user.service/ClassName.methodName or ./src/user/user.service/methodName
322
+ var parts = path.split('/').filter(function(p) { return p && p !== '.'; });
79
323
 
80
- while(relative_path[0] == '.') {
81
- relative_path = relative_path.substr(1);
324
+ if (parts.length < 1) {
325
+ Logger.printError('WHATAP-210',
326
+ 'Invalid hook_method_patterns format: ' + path +
327
+ '. Expected format: ./path/to/file/ClassName.methodName or ./path/to/file/methodName',
328
+ null, false);
329
+ return;
82
330
  }
331
+
332
+ var lastPart = parts.pop();
333
+ var methodName = null;
334
+ var className = null;
335
+
336
+ // Check if lastPart contains a dot (ClassName.methodName)
337
+ if (lastPart.indexOf('.') > 0) {
338
+ var classMethod = lastPart.split('.');
339
+ if (classMethod.length === 2) {
340
+ className = classMethod[0];
341
+ methodName = classMethod[1];
342
+ } else {
343
+ Logger.printError('WHATAP-211',
344
+ 'Invalid class.method format: ' + lastPart +
345
+ '. Expected format: ClassName.methodName',
346
+ null, false);
347
+ return;
348
+ }
349
+ } else {
350
+ // No dot, just a method name
351
+ methodName = lastPart;
352
+ }
353
+
354
+ var relative_path = '/' + parts.join('/');
355
+
83
356
  var root = process.cwd();
84
357
  if(root.indexOf('/bin') >= 0) {
85
358
  root = root.substr(0, root.indexOf('/bin'));
86
359
  }
87
- var real_path = root + relative_path;
88
- PackageCtrHelper.addPackage(real_path);
89
- if(loadedPackages[real_path]) {
90
- PackageCtrHelper.dynamicHook(loadedPackages[real_path], real_path, method);
91
- }
360
+
361
+ // Try multiple paths for TypeScript/ESM support
362
+ var pathsToTry = [
363
+ root + relative_path, // Original path
364
+ root + relative_path.replace('/src/', '/dist/'), // TypeScript compiled path
365
+ ];
366
+
367
+ // Add .js and .mjs extension variants
368
+ var extendedPaths = [];
369
+ pathsToTry.forEach(function(p) {
370
+ extendedPaths.push(p);
371
+ if (!p.endsWith('.js') && !p.endsWith('.ts') && !p.endsWith('.mjs')) {
372
+ extendedPaths.push(p + '.js');
373
+ extendedPaths.push(p + '.mjs');
374
+ }
375
+ });
376
+
377
+ // Async function to handle module loading and hooking
378
+ var tryLoadAndHook = function(pathIndex) {
379
+ if (pathIndex >= extendedPaths.length) {
380
+ Logger.printError('WHATAP-212',
381
+ 'Could not load module for hooking. Tried paths: ' + extendedPaths.join(', '),
382
+ null, false);
383
+ return;
384
+ }
385
+
386
+ var tryPath = extendedPaths[pathIndex];
387
+
388
+ try {
389
+ if (!fs.existsSync(tryPath) && !fs.existsSync(tryPath + '.js') && !fs.existsSync(tryPath + '.mjs')) {
390
+ // Path doesn't exist, try next
391
+ tryLoadAndHook(pathIndex + 1);
392
+ return;
393
+ }
394
+
395
+ // addPackage now returns a Promise
396
+ PackageCtrHelper.addPackage(tryPath).then(function() {
397
+ if (!loadedPackages[tryPath]) {
398
+ // Failed to load, try next path
399
+ tryLoadAndHook(pathIndex + 1);
400
+ return;
401
+ }
402
+
403
+ var moduleExports = loadedPackages[tryPath];
404
+
405
+ // Handle TypeScript/ES6 named exports
406
+ if (className) {
407
+ // For TypeScript classes: module exports { ClassName: [Class] }
408
+ if (moduleExports[className]) {
409
+ if (moduleExports[className].prototype && typeof moduleExports[className].prototype[methodName] === 'function') {
410
+ PackageCtrHelper.hookMethod(moduleExports[className].prototype, tryPath, methodName, className);
411
+ } else {
412
+ PackageCtrHelper.hookMethod(moduleExports[className], tryPath, methodName, className);
413
+ }
414
+ Logger.print('WHATAP-HOOK',
415
+ 'Hooked class method: ' + className + '.' + methodName,
416
+ false);
417
+ } else if (moduleExports.default) {
418
+ // ESM default export
419
+ if (typeof moduleExports.default === 'function' && moduleExports.default.name === className) {
420
+ // Default export is the class itself
421
+ if (moduleExports.default.prototype && typeof moduleExports.default.prototype[methodName] === 'function') {
422
+ PackageCtrHelper.hookMethod(moduleExports.default.prototype, tryPath, methodName, className);
423
+ } else {
424
+ PackageCtrHelper.hookMethod(moduleExports.default, tryPath, methodName, className);
425
+ }
426
+ Logger.print('WHATAP-HOOK',
427
+ 'Hooked ESM default class method: ' + className + '.' + methodName,
428
+ false);
429
+ } else if (moduleExports.default[className]) {
430
+ // Default export contains the class
431
+ if (moduleExports.default[className].prototype && typeof moduleExports.default[className].prototype[methodName] === 'function') {
432
+ PackageCtrHelper.hookMethod(moduleExports.default[className].prototype, tryPath, methodName, className);
433
+ } else {
434
+ PackageCtrHelper.hookMethod(moduleExports.default[className], tryPath, methodName, className);
435
+ }
436
+ Logger.print('WHATAP-HOOK',
437
+ 'Hooked ESM class method (in default): ' + className + '.' + methodName,
438
+ false);
439
+ } else {
440
+ Logger.printError('WHATAP-213',
441
+ 'Class "' + className + '" not found in module exports for ' + tryPath,
442
+ null, false);
443
+ }
444
+ } else {
445
+ Logger.printError('WHATAP-214',
446
+ 'Class "' + className + '" not found in module exports for ' + tryPath,
447
+ null, false);
448
+ }
449
+ } else {
450
+ // Direct method or try to find the method in exports
451
+ // For ESM, check default export first
452
+ if (moduleExports.default && typeof moduleExports.default === 'object') {
453
+ if (typeof moduleExports.default[methodName] === 'function') {
454
+ PackageCtrHelper.hookMethod(moduleExports.default, tryPath, methodName, null);
455
+ } else {
456
+ PackageCtrHelper.dynamicHook(moduleExports.default, tryPath, methodName);
457
+ }
458
+ Logger.print('WHATAP-HOOK',
459
+ 'Hooked ESM default export method: ' + tryPath + '/' + methodName,
460
+ false);
461
+ } else {
462
+ if (typeof moduleExports[methodName] === 'function') {
463
+ PackageCtrHelper.hookMethod(moduleExports, tryPath, methodName, null);
464
+ } else {
465
+ PackageCtrHelper.dynamicHook(moduleExports, tryPath, methodName);
466
+ }
467
+ Logger.print('WHATAP-HOOK',
468
+ 'Hooked method: ' + tryPath + '/' + methodName,
469
+ false);
470
+ }
471
+ }
472
+ }).catch(function(e) {
473
+ // Error loading this path, try next
474
+ Logger.printError('WHATAP-215',
475
+ 'Error loading module ' + tryPath + ': ' + e.message,
476
+ e, false);
477
+ tryLoadAndHook(pathIndex + 1);
478
+ });
479
+ } catch (e) {
480
+ // Error with this path, try next
481
+ tryLoadAndHook(pathIndex + 1);
482
+ }
483
+ };
484
+
485
+ // Start trying paths from index 0
486
+ tryLoadAndHook(0);
92
487
  });
93
- });
488
+ })();
94
489
 
95
- module.exports = PackageCtrHelper;
490
+ module.exports = PackageCtrHelper;