analogger 1.37.0 β†’ 2.0.0

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.
@@ -148,6 +148,7 @@ const symbolNames = {
148
148
  lock : "πŸ”’",
149
149
  male_sign : "β™‚",
150
150
  minus_sign : "βž–",
151
+ money_bag : "πŸ’°",
151
152
  no_entry : "β›”",
152
153
  old_key : "πŸ—οΈ",
153
154
  partly_sunny : "β›…",
@@ -401,6 +402,153 @@ function createTarGzArchiveSync(inputFile, archivePath, compressionLevel = 1) {
401
402
  }
402
403
  }
403
404
 
405
+ function getInvocationLine()
406
+ {
407
+ try
408
+ {
409
+ const error = new Error();
410
+ const stack = error.stack;
411
+ let anaLoggerPossibleFileName = "ana-logger";
412
+ let isMinified = false;
413
+ let result = null;
414
+ let strippedOutStackTrace;
415
+ let errorText = [];
416
+
417
+ if (stack) {
418
+ const lines = stack.split('\n');
419
+ let fileName = null;
420
+ if (lines.length >= 3) {
421
+ let index = 0;
422
+ // Look for the reference of this line
423
+ for (let i = 0, l = lines.length; i < l; i++) {
424
+ const line = lines[i];
425
+
426
+ // We can't use the name if it's minified
427
+ const parts = line.split(':');
428
+ if (parts.length < 3) {
429
+ errorText.push(line);
430
+ continue;
431
+ }
432
+
433
+ index = i;
434
+
435
+ fileName = parts[parts.length - 3];
436
+
437
+ if (line.indexOf(anaLoggerPossibleFileName)=== -1) {
438
+ // We're sure we can use this stacktrace
439
+ break;
440
+ }
441
+
442
+ if (line.indexOf("getInvocationLine")=== -1) {
443
+ // The file is minified
444
+ // We're only partially sure we can use this stacktrace
445
+ isMinified = true;
446
+ // We try to extract the file name
447
+ anaLoggerPossibleFileName = fileName.split(/[\\/]/).pop();
448
+ break;
449
+ }
450
+
451
+ // We have no idea if the stacktrace will help us, but we leave the search
452
+ break;
453
+ }
454
+
455
+ // Look for when the call was done exactly
456
+ for (let i = index + 1, l = lines.length; i < l; i++) {
457
+ const lineStr = lines[i];
458
+ if (lineStr.indexOf(fileName) > -1) {
459
+ continue;
460
+ }
461
+
462
+ const parts = lineStr.split(':');
463
+ if (parts.length < 3) {
464
+ continue;
465
+ }
466
+
467
+ strippedOutStackTrace = errorText.join("\n") + lines.slice(i).join('\n');
468
+
469
+ const col = parseInt(parts.pop());
470
+ const line = parseInt(parts.pop());
471
+ const file = parts.pop();
472
+ let infoStr = parts.pop();
473
+ let infoArr = infoStr.split(" ");
474
+
475
+ let method = null;
476
+ for (let j = 0; j < infoArr.length; j++) {
477
+ const element = infoArr[j];
478
+ if (!element) {
479
+ continue;
480
+ }
481
+
482
+ if (element.indexOf("at")===0) {
483
+ continue;
484
+ }
485
+
486
+ method = element;
487
+ break;
488
+ }
489
+
490
+ result = {
491
+ file,
492
+ line,
493
+ col,
494
+ method,
495
+ isMinified,
496
+ stack: strippedOutStackTrace
497
+ }
498
+
499
+ break;
500
+ }
501
+
502
+ return result;
503
+ }
504
+ }
505
+ }
506
+ catch(err)
507
+ {
508
+
509
+ }
510
+
511
+ return null;
512
+ }
513
+
514
+ function generateLid(maxChars = 8)
515
+ {
516
+ try
517
+ {
518
+ const line = getInvocationLine();
519
+ if (!line) {
520
+ return `LID${Date.now()}`;
521
+ }
522
+ const fun = line.method.split(".");
523
+ const id = fun[0].toUpperCase().substring(0, 3);
524
+ if (id.length >= maxChars) {
525
+ return id.substring(0, maxChars);
526
+ }
527
+
528
+ let combined1 = `${id}:${line.line}`;
529
+ if (combined1.length >= maxChars) {
530
+ combined1 = combined1.replaceAll(":", "");
531
+ return combined1.substring(0, maxChars);
532
+ }
533
+
534
+ let combined2 = `${id}:${line.line}:${line.col}`;
535
+ if (combined2.length > maxChars) {
536
+ const combined3 = combined2.substring(0, maxChars);
537
+ if (combined3.endsWith(":")) {
538
+ return `${id}${line.line}:${line.col}`.substring(0, maxChars);
539
+ }
540
+
541
+ return combined2.substring(0, maxChars);
542
+ }
543
+
544
+ return `${id}${line.line}:${line.col}`.substring(0, maxChars);
545
+ }
546
+ catch (e) {
547
+ return `ERR_LID${Date.now()}`;
548
+ }
549
+ }
550
+
551
+
404
552
  /**
405
553
  * https://stackoverflow.com/questions/17575790/environment-detection-node-js-or-browser
406
554
  * @returns {string}
@@ -518,6 +666,12 @@ class ____AnaLogger
518
666
 
519
667
  originalFormatFunction;
520
668
 
669
+ static lidTable = {};
670
+ static lidTableOn = false;
671
+
672
+ forceLidOn = false;
673
+ resolveLineCall = false;
674
+ resolveErrorLineCall = false;
521
675
 
522
676
  constructor({name = "default"} = {})
523
677
  {
@@ -571,6 +725,79 @@ class ____AnaLogger
571
725
  return this.instanceId;
572
726
  }
573
727
 
728
+ /**
729
+ * For the logger to generate a lid when none is specified
730
+ * @param lidOn
731
+ */
732
+ forceLid(lidOn = true)
733
+ {
734
+ this.forceLidOn = !!lidOn;
735
+ }
736
+
737
+ forceResolveLineCall(resolveLineCall = true)
738
+ {
739
+ this.resolveLineCall = !!resolveLineCall;
740
+ }
741
+
742
+ forceResolveErrorLineCall(resolveErrorLineCall = true)
743
+ {
744
+ this.resolveErrorLineCall = !!resolveErrorLineCall;
745
+ }
746
+
747
+ importLids(lids)
748
+ {
749
+ for (let lid in lids)
750
+ {
751
+ const lidObj = lids[lid] || {};
752
+ lidObj.lid = lid;
753
+ lidObj.callCount = 0;
754
+ lidObj.callTimes = [];
755
+ ____AnaLogger.lidTable[lid] = lidObj;
756
+ }
757
+ ____AnaLogger.lidTableOn = true;
758
+ }
759
+
760
+ loadLids(lids)
761
+ {
762
+ lids = lids || {};
763
+ this.importLids(lids);
764
+ }
765
+
766
+ convertTimestampToDate(timestamp)
767
+ {
768
+ const date = new Date(timestamp); // Create a Date object from the timestamp
769
+
770
+ const year = date.getFullYear();
771
+ const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
772
+ const day = String(date.getDate()).padStart(2, '0');
773
+ const hours = String(date.getHours()).padStart(2, '0');
774
+ const minutes = String(date.getMinutes()).padStart(2, '0');
775
+ const seconds = String(date.getSeconds()).padStart(2, '0');
776
+ const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
777
+
778
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
779
+ }
780
+
781
+ getLids()
782
+ {
783
+ const clone = {...__AnaLogger.lidTable };
784
+ for (let lid in clone)
785
+ {
786
+ const lidObj = clone[lid] || {};
787
+ if (lidObj.callTimes.length)
788
+ {
789
+ lidObj.dates = [];
790
+ for (let j = 0; j < lidObj.callTimes.length; ++j)
791
+ {
792
+ const callTime = lidObj.callTimes[j];
793
+ const readableTime = this.convertTimestampToDate(callTime);
794
+ lidObj.dates.push(readableTime);
795
+ }
796
+ }
797
+ }
798
+ return clone;
799
+ }
800
+
574
801
  keepLogHistory()
575
802
  {
576
803
  this.keepLog = true;
@@ -2005,18 +2232,74 @@ class ____AnaLogger
2005
2232
  }
2006
2233
  }
2007
2234
 
2235
+ isContextMessagePattern(str) {
2236
+ return /\{\{[^}]+}}/.test(str);
2237
+ }
2238
+
2239
+ transformContextMessage(template, data)
2240
+ {
2241
+ let result = template;
2242
+ for (const key in data)
2243
+ {
2244
+ const placeholder = `{{${key}}}`;
2245
+ const value = data[key];
2246
+ // Replace all occurrences of the placeholder with the value
2247
+ result = result.replaceAll(placeholder, value);
2248
+ }
2249
+ return result;
2250
+ }
2251
+
2008
2252
  /**
2009
2253
  * Display log following template
2010
2254
  * @param context
2255
+ * @param argsWithoutContext
2011
2256
  */
2012
- processOutput(context = {})
2257
+ processOutput(context = {}, ...argsWithoutContext)
2013
2258
  {
2014
2259
  try
2015
2260
  {
2016
2261
  let message = "";
2262
+
2263
+ if (____AnaLogger.lidTableOn && context.lid) {
2264
+ const lidObj = ____AnaLogger.lidTable[context.lid];
2265
+
2266
+ // If the lid is already in the table, we set the message if not existing
2267
+ if (lidObj)
2268
+ {
2269
+ context.message = context.message || lidObj.message;
2270
+ lidObj.callCount = lidObj.callCount || 0;
2271
+ ++lidObj.callCount;
2272
+ lidObj.callTimes.push(Date.now());
2273
+ }
2274
+ // If the lid is not already in the table, we register it
2275
+ else {
2276
+ ____AnaLogger.lidTable[context.lid] = {
2277
+ ...context,
2278
+ message: argsWithoutContext[0],
2279
+ lid: context.lid,
2280
+ callCount: 1,
2281
+ callTimes: [Date.now()]
2282
+ }
2283
+ }
2284
+ }
2285
+
2286
+ if (context.message) {
2287
+ // If the context message is a template
2288
+ if (this.isContextMessagePattern(context.message)) {
2289
+ // The context message second parameter should be an object that we will use to perform the replacement
2290
+ if (argsWithoutContext.length >= 1 && typeof argsWithoutContext[0] === "object") {
2291
+ context.message = this.transformContextMessage(context.message, argsWithoutContext[0]);
2292
+ // We remove the second parameter from the arguments
2293
+ argsWithoutContext.shift();
2294
+ }
2295
+ }
2296
+
2297
+ // We add the context message as an arguments
2298
+ argsWithoutContext.unshift(context.message);
2299
+ }
2017
2300
  this.applySymbolByName(context);
2018
2301
 
2019
- this.checkOnLogging(context, context, arguments, "onContext");
2302
+ this.checkOnLogging(context, context, argsWithoutContext, "onContext");
2020
2303
  if (!this.isTargetAllowed(context.target))
2021
2304
  {
2022
2305
  return;
@@ -2032,12 +2315,12 @@ class ____AnaLogger
2032
2315
  return;
2033
2316
  }
2034
2317
 
2035
- // Clone arguments without the context (= the first argument passed) to generate the message
2036
- const newMessages = this.checkOnLogging(context, arguments && arguments.length > 1? arguments[1] : arguments, arguments,"onMessage");
2318
+ const newMessages = this.checkOnLogging(context, argsWithoutContext[0], arguments,"onMessage");
2037
2319
  if (newMessages !== undefined) {
2038
2320
  arguments[1] = newMessages;
2039
2321
  }
2040
- let args = Array.prototype.slice.call(arguments, 1 /* => Ignore arguments[0] = context */);
2322
+
2323
+ let args = argsWithoutContext;
2041
2324
 
2042
2325
  message = this.convertArgumentsToText(args);
2043
2326
 
@@ -2144,6 +2427,12 @@ class ____AnaLogger
2144
2427
  options.hasOwnProperty("lid");
2145
2428
  }
2146
2429
 
2430
+ /**
2431
+ * Convert a string to an object by parsing the string
2432
+ * and identifying key-value pairs
2433
+ * @param str
2434
+ * @returns {{}|null}
2435
+ */
2147
2436
  stringToObject(str) {
2148
2437
  try {
2149
2438
  str = str.trim();
@@ -2208,39 +2497,39 @@ class ____AnaLogger
2208
2497
  /**
2209
2498
  * Convert a string into a Context object if possible
2210
2499
  * TODO: To implement in next version
2211
- * @param str
2212
2500
  * @returns {string}
2501
+ * @param input
2213
2502
  */
2214
- extractContextFromInput(str)
2503
+ extractContextFromInput(input)
2215
2504
  {
2216
- if (typeof str === "string" || str instanceof String)
2505
+ if (typeof input === "string" || input instanceof String)
2217
2506
  {
2218
- if (str.toLowerCase().indexOf("lid:") !== 0)
2507
+ if (input.toLowerCase().indexOf("lid:") !== 0)
2219
2508
  {
2220
- return str;
2509
+ return input;
2221
2510
  }
2222
2511
 
2223
- const obj = this.stringToObject(str);
2512
+ const obj = this.stringToObject(input);
2224
2513
  if (obj) {
2225
- str = obj;
2514
+ input = obj;
2226
2515
  }
2227
2516
  }
2228
2517
 
2229
- if (typeof str==="object" && !Array.isArray(str) && str!==null) {
2230
- if (this.isExtendedOptionsPassed(str)) {
2231
- if (str.contextName) {
2232
- const obj = this.#contexts[str.contextName];
2518
+ if (typeof input==="object" && !Array.isArray(input) && input!==null) {
2519
+ if (this.isExtendedOptionsPassed(input)) {
2520
+ if (input.contextName) {
2521
+ const obj = this.#contexts[input.contextName];
2233
2522
  if (obj) {
2234
- str = Object.assign({}, obj, str);
2523
+ input = Object.assign({}, obj, input);
2235
2524
  }
2236
2525
  }
2237
- if (!str.target) {
2238
- str.target = this.getActiveTarget();
2526
+ if (!input.target) {
2527
+ input.target = this.getActiveTarget();
2239
2528
  }
2240
2529
  }
2241
2530
  }
2242
2531
 
2243
- return str;
2532
+ return input;
2244
2533
  }
2245
2534
 
2246
2535
  listSymbols()
@@ -2302,13 +2591,25 @@ class ____AnaLogger
2302
2591
  log(options, ...args)
2303
2592
  {
2304
2593
  options = this.extractContextFromInput(options);
2594
+
2305
2595
  // If the first parameter is not of context format,
2306
2596
  // We use the default context
2307
2597
  if (!this.isExtendedOptionsPassed(options))
2308
2598
  {
2309
- const defaultContext = this.generateDefaultContext();
2310
- this.processOutput.apply(this, [defaultContext, options, ...args]);
2311
- return;
2599
+ if (!this.forceLidOn)
2600
+ {
2601
+ const defaultContext = this.generateDefaultContext();
2602
+ this.processOutput.apply(this, [defaultContext, options, ...args]);
2603
+ return;
2604
+ }
2605
+
2606
+ if (!args || !args.length)
2607
+ {
2608
+ args = [options];
2609
+ }
2610
+
2611
+ const newLid = generateLid(this.options.lidLenMax);
2612
+ options = {lid: newLid};
2312
2613
  }
2313
2614
 
2314
2615
  const someContext = this.generateDefaultContext();
@@ -2320,6 +2621,10 @@ class ____AnaLogger
2320
2621
  return;
2321
2622
  }
2322
2623
 
2624
+ if (this.resolveLineCall)
2625
+ {
2626
+ context.stack = getInvocationLine();
2627
+ }
2323
2628
  this.processOutput.apply(this, [context, ...args]);
2324
2629
  }
2325
2630
 
@@ -2336,16 +2641,25 @@ class ____AnaLogger
2336
2641
  // We use the error context and display
2337
2642
  if (!this.isExtendedOptionsPassed(options))
2338
2643
  {
2339
- const defaultContext = this.generateErrorContext();
2340
- this.processOutput.apply(this, [defaultContext, options, ...args]);
2341
- return;
2644
+ if (!this.forceLidOn)
2645
+ {
2646
+ const defaultContext = this.generateErrorContext();
2647
+ this.processOutput.apply(this, [defaultContext, options, ...args]);
2648
+ return;
2649
+ }
2650
+
2651
+ const newLid = generateLid(this.options.lidLenMax);
2652
+ options = {lid: newLid};
2342
2653
  }
2343
2654
 
2344
2655
  const errorContext = this.generateErrorContext();
2345
2656
  let context = this.convertToContext(options, errorContext);
2346
2657
 
2347
- let args0 = Array.prototype.slice.call(arguments, 1);
2348
- this.log(context, ...args0);
2658
+ if (this.resolveErrorLineCall)
2659
+ {
2660
+ context.stack = getInvocationLine();
2661
+ }
2662
+ this.log(context, ...args);
2349
2663
  }
2350
2664
 
2351
2665
  overrideError()
package/demo.cjs CHANGED
@@ -11,9 +11,60 @@ anaLogger.setOptions({
11
11
  compressArchives: true,
12
12
  });
13
13
 
14
+ const LIDS = {
15
+ API35390: {
16
+ message: "API logging initialized",
17
+ contextName: "TEST",
18
+ color : "green",
19
+ symbol : "check"
20
+ },
21
+ API35391: {
22
+ message: "Error API logging initialized",
23
+ },
24
+ API65341: {
25
+ message: "The username doesn't match the userID: {{username1}} !== {{username2}}"
26
+ }
27
+ };
28
+
29
+ anaLogger.loadLids(LIDS);
30
+
31
+ anaLogger.forceLid(true);
32
+ anaLogger.forceResolveLineCall(true);
33
+ anaLogger.forceResolveErrorLineCall(true);
34
+ anaLogger.log({lid: "AAA65422"}, "There will be a lid generated for us here");
35
+ anaLogger.log("There will be a lid generated for us here");
36
+ anaLogger.log({}, "There will be a lid generated for us here");
37
+ anaLogger.log({aaa: 1}, "There will be a lid generated for us here");
38
+
39
+ anaLogger.log({lid: "API35390", color: "green"}, "API logging about to be initialized");
40
+ // => [12:29:26] DEFAULT: (API35390) βœ” API logging about to be initialized
41
+
42
+ anaLogger.log(LIDS.API35390);
43
+ // => [12:29:26] TEST: (API35390) βœ” API logging initialized
44
+
45
+ anaLogger.error(LIDS.API35391);
46
+ // => [12:29:26] ERROR: (API35391) ❌ Error API logging initialized
47
+
48
+ anaLogger.log(LIDS.API65341, {username1: "test", username2: "test2"});
49
+ // => [12:29:26] DEFAULT: (API65341) βœ” The username doesn't match the userID: test !== test2
50
+
51
+ anaLogger.log(LIDS.API65341, "Some other messages");
52
+ // => [12:30:40] DEFAULT: (API65341) βœ” The username doesn't match the userID: {{username1}} !== {{username2}}β€’Some other messages
53
+
54
+ anaLogger.log({lid: "WEB35382", color: "yellow"}, `Some log message`);
55
+ anaLogger.log({ lid: "WEB35382" });
56
+
57
+ anaLogger.log({lid: "WEB35382", color: "yellow"}, `Log with template {{example1}}`);
58
+ anaLogger.log({ lid: "WEB35382" }, {example1: "TEST"});
59
+ anaLogger.log({ lid: "WEB35382" }, {example1: "TEST"}, "rrr");
60
+
61
+ console.log(anaLogger.getLids());
62
+
14
63
  anaLogger.setContext("TEST", { color: "#5d8a6b", symbol: "email", name: "TEST", lid: "12222" });
15
64
  anaLogger.log({lid: "WEB35382", color: "yellow"}, `Log with target`);
65
+
16
66
  anaLogger.log({contextName: "TEST"}, `Test Log example TEST`);
67
+
17
68
  anaLogger.log({contextName: "TEST"}, `Test Log example TEST 2`);
18
69
 
19
70
  anaLogger.log({lid: "WEB35380", color: "yellow"}, {MY: "TEST"}, `Log with target`);
@@ -85,4 +136,5 @@ for (let i = 1; i < 10; ++i) {
85
136
  lid: "WEB35382",
86
137
  target: "localhost"
87
138
  }, `${i}: Lorem Ipsum is simply dummy text.`);
88
- }
139
+ }
140
+