syntropylog 0.9.12 → 0.9.14

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/CONTRIBUTING.md +18 -0
  3. package/README.md +12 -9
  4. package/dist/index.cjs +106 -15
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.ts +40 -3
  7. package/dist/index.mjs +105 -15
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/logger/transports/BaseConsolePrettyTransport.js +2 -7
  10. package/dist/logger/transports/BaseConsolePrettyTransport.js.map +1 -1
  11. package/dist/logger/transports/ColorfulConsoleTransport.js.map +1 -1
  12. package/dist/logger/transports/CompactConsoleTransport.js +1 -1
  13. package/dist/logger/transports/CompactConsoleTransport.js.map +1 -1
  14. package/dist/logger/transports/PrettyConsoleTransport.js.map +1 -1
  15. package/dist/logger/transports/optionalChalk.js +55 -0
  16. package/dist/logger/transports/optionalChalk.js.map +1 -0
  17. package/dist/masking/MaskingEngine.js +44 -9
  18. package/dist/masking/MaskingEngine.js.map +1 -1
  19. package/dist/redis/RedisConnectionManager.js +4 -0
  20. package/dist/redis/RedisConnectionManager.js.map +1 -1
  21. package/dist/redis/RedisManager.js +2 -0
  22. package/dist/redis/RedisManager.js.map +1 -1
  23. package/dist/testing/index.d.ts +15 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/dist/types/logger/transports/BaseConsolePrettyTransport.d.ts +3 -2
  26. package/dist/types/logger/transports/ColorfulConsoleTransport.d.ts +3 -3
  27. package/dist/types/logger/transports/PrettyConsoleTransport.d.ts +3 -3
  28. package/dist/types/logger/transports/optionalChalk.d.ts +27 -0
  29. package/dist/types/masking/MaskingEngine.d.ts +15 -1
  30. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.14
4
+
5
+ ### Patch Changes
6
+
7
+ - **Chalk optional for pretty console transports (Classic, Pretty, Compact, Colorful)**
8
+ - **Fix**: `ClassicConsoleTransport` (and other chalk-powered transports) now work in both ESM (tsx + `"type": "module"`) and CJS (e.g. ts-node) consumers. Chalk is loaded optionally via a small helper that uses `require` in CJS and `createRequire(import.meta.url)` in ESM; if chalk is missing or fails to load, a no-op is used so the same format is logged without colors.
9
+ - **README**: Clarified that chalk is optional — install it for colors, or use the same transports without it for plain-text output. Table updated to show "With chalk" / "Without chalk" and added ColorfulConsoleTransport.
10
+
11
+ ## 0.9.13
12
+
13
+ ### Patch Changes
14
+
15
+ - a1498cb: - **MaskingEngine**: On masking failure (timeout/error), return a safe fallback payload with `_maskingFailed` and allowed keys only (`level`, `timestamp`, `message`, `service`) instead of raw metadata to avoid leaking sensitive data.
16
+ - **RedisConnectionManager**: Call `removeAllListeners()` when client was never open in `disconnect()` to avoid listener leaks.
17
+ - **RedisManager**: Clear `instances` and `defaultInstance` in `shutdown()` after closing connections.
18
+ - eca5f56: **Fix: ~3–6s delay per log call (logger.info/warn/error)**
19
+ - **Cause**: `MaskingEngine` used the `regex-test` package for every key×rule check. That package runs each test in a child-process worker with a single queue, so many sequential IPC round-trips added up to several seconds per log.
20
+ - **Change**: Built-in default rules (password, email, token, credit_card, SSN, phone) now use synchronous `RegExp.test()` in-process; they use safe, known patterns with no ReDoS risk. Custom rules added via `masking.rules` still use `regex-test` with timeout for safety.
21
+ - **Result**: Log calls complete in milliseconds again. README documents the behavior under "Data Masking → Performance".
22
+
3
23
  All notable changes to this project will be documented in this file.
4
24
 
5
25
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
package/CONTRIBUTING.md CHANGED
@@ -86,6 +86,24 @@ We welcome code contributions! To contribute code, please follow these steps:
86
86
  - Keep API documentation current
87
87
  - Include usage examples
88
88
 
89
+ ## Release process (maintainers)
90
+
91
+ Releases use [Changesets](https://github.com/changesets/changesets) and GitHub Actions. **Para que la versión suba en npm, siempre tienes que hacer una de estas dos cosas:**
92
+
93
+ ### Opción A: Usar el PR que crea el action
94
+
95
+ 1. **Añade un changeset** al cambiar la librería: `pnpm changeset` (elige tipo de versión y describe el cambio).
96
+ 2. **Push a `main`**. El workflow crea un PR **"Version Packages"** con el bump (ej. 0.9.12 → 0.9.13) y el CHANGELOG actualizado. **Todavía no publica en npm.**
97
+ 3. **Mergea ese PR** en `main`. Ese merge vuelve a disparar el workflow y **ahí sí** se ejecuta `publish` y la nueva versión aparece en npm.
98
+
99
+ ### Opción B: Subir la versión a mano (sin PR)
100
+
101
+ 1. Con changesets ya en el repo, en local: `pnpm run version-packages`. Eso actualiza `package.json`, CHANGELOG y borra los changesets.
102
+ 2. **Commit** (package.json, CHANGELOG.md, y los .changeset/*.md borrados) y **push a `main`**.
103
+ 3. El workflow corre, no hay changesets que aplicar, y ejecuta **publish** → la versión sube a npm.
104
+
105
+ En ambos casos, **main** y **npm** quedan con la misma versión.
106
+
89
107
  ## Getting Help
90
108
 
91
109
  If you need help with your contribution, please:
package/README.md CHANGED
@@ -73,24 +73,25 @@ npm install syntropylog
73
73
 
74
74
  By default, SyntropyLog outputs **lightweight plain JSON to the console — automatically, with no configuration needed**. No imports, no setup, no extra dependencies.
75
75
 
76
- If you want **colored, human-readable output** for development, use one of the chalk-powered transports. These require `chalk` to be installed explicitly in your project to keep the base bundle small:
76
+ If you want **colored, human-readable output** for development, use one of the pretty console transports (`ClassicConsoleTransport`, `PrettyConsoleTransport`, `CompactConsoleTransport`, `ColorfulConsoleTransport`). **Chalk is optional:** if you install `chalk` in your project, those transports use it and you get colors; if you don't install it, the same format is shown in plain text (no colors). That keeps the base bundle small and avoids CJS/ESM load issues when chalk isn't present.
77
77
 
78
78
  ```bash
79
- npm install chalk
79
+ npm install chalk # optional — only if you want colors
80
80
  ```
81
81
 
82
- | Transport | Style | Color | Recommended for |
83
- | :--- | :--- | :---: | :--- |
84
- | *(default)* | Plain JSON | | Production / log aggregators |
85
- | `ClassicConsoleTransport` | Structured + colored | ✅ | Development |
86
- | `PrettyConsoleTransport` | Human-readable pretty print | ✅ | Development / debugging |
87
- | `CompactConsoleTransport` | Compact one-liner | ✅ | Development |
82
+ | Transport | Style | With chalk | Without chalk | Recommended for |
83
+ | :--- | :--- | :---: | :--- | :--- |
84
+ | *(default)* | Plain JSON | | — | Production / log aggregators |
85
+ | `ClassicConsoleTransport` | Structured single-line | ✅ Colored | Plain text | Development |
86
+ | `PrettyConsoleTransport` | Human-readable pretty | ✅ Colored | Plain text | Development / debugging |
87
+ | `CompactConsoleTransport` | Compact one-liner | ✅ Colored | Plain text | Development |
88
+ | `ColorfulConsoleTransport` | Full-line colored | ✅ Colored | Plain text | Development |
88
89
 
89
90
  ```typescript
90
91
  // Default — no import needed, works out of the box
91
92
  syntropyLog.init({ logger: { level: 'info', serviceName: 'my-app' } });
92
93
 
93
- // Want colors? Install chalk first, then import the transport:
94
+ // Pretty format: with chalk → colors; without chalk same format, no colors
94
95
  import { ClassicConsoleTransport } from 'syntropylog';
95
96
 
96
97
  syntropyLog.init({
@@ -446,6 +447,8 @@ await syntropyLog.init({
446
447
 
447
448
  > **Silent Observer guarantee**: if the masking engine fails for any reason, it returns the original object and the application keeps running — it never throws.
448
449
 
450
+ **Performance**: Built-in rules use synchronous regex matching (safe, known patterns). Custom rules you add still use the timeout-protected `regex-test` worker to guard against ReDoS. This avoids the ~3–6s delay per log that occurred when every key was tested via the worker queue.
451
+
449
452
  ---
450
453
 
451
454
  ## 💾 Universal Persistence — Log to Any Database
package/dist/index.cjs CHANGED
@@ -7,9 +7,10 @@ var node_async_hooks = require('node:async_hooks');
7
7
  var crypto = require('crypto');
8
8
  var util = require('node:util');
9
9
  var flatted = require('flatted');
10
- var chalk = require('chalk');
10
+ var module$1 = require('module');
11
11
  var redis = require('redis');
12
12
 
13
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
14
  function _interopNamespaceDefault(e) {
14
15
  var n = Object.create(null);
15
16
  if (e) {
@@ -150,36 +151,42 @@ class MaskingEngine {
150
151
  strategy: MaskingStrategy.CREDIT_CARD,
151
152
  preserveLength: true,
152
153
  maskChar: this.maskChar,
154
+ _isDefaultRule: true,
153
155
  },
154
156
  {
155
157
  pattern: /ssn|social_security|security_number/i,
156
158
  strategy: MaskingStrategy.SSN,
157
159
  preserveLength: true,
158
160
  maskChar: this.maskChar,
161
+ _isDefaultRule: true,
159
162
  },
160
163
  {
161
164
  pattern: /email/i,
162
165
  strategy: MaskingStrategy.EMAIL,
163
166
  preserveLength: true,
164
167
  maskChar: this.maskChar,
168
+ _isDefaultRule: true,
165
169
  },
166
170
  {
167
171
  pattern: /phone|phone_number|mobile_number/i,
168
172
  strategy: MaskingStrategy.PHONE,
169
173
  preserveLength: true,
170
174
  maskChar: this.maskChar,
175
+ _isDefaultRule: true,
171
176
  },
172
177
  {
173
178
  pattern: /password|pass|pwd|secret/i,
174
179
  strategy: MaskingStrategy.PASSWORD,
175
180
  preserveLength: true,
176
181
  maskChar: this.maskChar,
182
+ _isDefaultRule: true,
177
183
  },
178
184
  {
179
185
  pattern: /token|api_key|auth_token|jwt|bearer/i,
180
186
  strategy: MaskingStrategy.TOKEN,
181
187
  preserveLength: true,
182
188
  maskChar: this.maskChar,
189
+ _isDefaultRule: true,
183
190
  },
184
191
  ];
185
192
  for (const rule of defaultRules) {
@@ -206,8 +213,10 @@ class MaskingEngine {
206
213
  /**
207
214
  * Processes a metadata object and applies the configured masking rules.
208
215
  * Uses JSON flattening strategy for extreme performance.
216
+ * On failure (timeout, rule error, etc.) returns a safe redacted object with an explicit message
217
+ * instead of the original data, to avoid leaking sensitive content.
209
218
  * @param meta - The metadata object to process
210
- * @returns A new object with the masked data
219
+ * @returns A new object with the masked data, or a safe fallback object if masking fails
211
220
  */
212
221
  async process(meta) {
213
222
  // Set initialized flag on first use
@@ -222,10 +231,28 @@ class MaskingEngine {
222
231
  return masked;
223
232
  }
224
233
  catch {
225
- // Silent observer - return original data if masking fails
226
- return meta;
234
+ // Do not return original data: emit a safe placeholder so sensitive payload is never logged
235
+ return {
236
+ ...MaskingEngine.buildSafeFallbackFromMeta(meta),
237
+ _maskingFailed: true,
238
+ _maskingFailedMessage: MaskingEngine.MASKING_FAILED_MESSAGE,
239
+ };
227
240
  }
228
241
  }
242
+ /**
243
+ * Builds a minimal safe object from meta (level, timestamp, message, service) for fallback.
244
+ * Avoids leaking any arbitrary keys/values when masking fails.
245
+ */
246
+ static buildSafeFallbackFromMeta(meta) {
247
+ const safe = {};
248
+ const allowedKeys = ['level', 'timestamp', 'message', 'service'];
249
+ for (const key of allowedKeys) {
250
+ if (key in meta && meta[key] !== undefined) {
251
+ safe[key] = meta[key];
252
+ }
253
+ }
254
+ return safe;
255
+ }
229
256
  /**
230
257
  * Applies masking rules to data recursively.
231
258
  * @param data - Data to mask
@@ -254,13 +281,20 @@ class MaskingEngine {
254
281
  for (const rule of this.rules) {
255
282
  let isMatch = false;
256
283
  if (rule._compiledPattern) {
257
- try {
258
- // Use regex-test for safe execution with timeout
259
- isMatch = await this.regexTest.test(rule._compiledPattern, key);
284
+ if (rule._isDefaultRule) {
285
+ // Default rules use safe, known patterns (no ReDoS); sync test avoids
286
+ // regex-test worker IPC queue which caused ~3–6s delay per log.
287
+ isMatch = rule._compiledPattern.test(key);
260
288
  }
261
- catch {
262
- // Silent failure on timeout/error - treat as no match
263
- isMatch = false;
289
+ else {
290
+ try {
291
+ // Custom rules: use regex-test for safe execution with timeout
292
+ isMatch = await this.regexTest.test(rule._compiledPattern, key);
293
+ }
294
+ catch {
295
+ // Silent failure on timeout/error - treat as no match
296
+ isMatch = false;
297
+ }
264
298
  }
265
299
  }
266
300
  if (isMatch) {
@@ -477,6 +511,8 @@ class MaskingEngine {
477
511
  }
478
512
  }
479
513
  }
514
+ /** Message used when masking fails (e.g. timeout) so we never emit raw payload. */
515
+ MaskingEngine.MASKING_FAILED_MESSAGE = '[SyntropyLog] Masking could not be applied (e.g. timeout or error); payload redacted for safety.';
480
516
 
481
517
  /**
482
518
  * FILE: src/config.schema.ts
@@ -2519,9 +2555,59 @@ class SyntropyLog extends events.EventEmitter {
2519
2555
  const syntropyLog = SyntropyLog.getInstance();
2520
2556
 
2521
2557
  /**
2522
- * @file src/logger/transports/BaseConsolePrettyTransport.ts
2523
- * @description An abstract base class for console transports that provide colored, human-readable output.
2558
+ * @file src/logger/transports/optionalChalk.ts
2559
+ * @description Load chalk optionally so that pretty console transports work in both
2560
+ * ESM (tsx + "type": "module") and CJS (ts-node) consumers. If chalk is missing or
2561
+ * fails to load, a no-op identity is used (no colors).
2562
+ */
2563
+ function createNoColorChalk() {
2564
+ const noColor = ((s) => s);
2565
+ noColor.white = noColor;
2566
+ noColor.bold = noColor;
2567
+ noColor.red = noColor;
2568
+ noColor.bgRed = noColor;
2569
+ noColor.yellow = noColor;
2570
+ noColor.cyan = noColor;
2571
+ noColor.green = noColor;
2572
+ noColor.gray = noColor;
2573
+ noColor.magenta = noColor;
2574
+ noColor.blue = noColor;
2575
+ noColor.bgWhite = noColor;
2576
+ noColor.dim = noColor;
2577
+ return noColor;
2578
+ }
2579
+ function getRequire() {
2580
+ if (typeof require !== 'undefined') {
2581
+ return require;
2582
+ }
2583
+ return module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
2584
+ }
2585
+ function loadChalkSync() {
2586
+ try {
2587
+ const c = getRequire()('chalk');
2588
+ const ch = (c?.default ?? c);
2589
+ if (ch && typeof ch.white !== 'undefined') {
2590
+ return ch;
2591
+ }
2592
+ }
2593
+ catch {
2594
+ // chalk not installed or failed to load (e.g. CJS/ESM interop)
2595
+ }
2596
+ return createNoColorChalk();
2597
+ }
2598
+ let cached = null;
2599
+ /**
2600
+ * Returns a chalk-like instance: real chalk if available and usable, otherwise
2601
+ * a no-op that returns the string unchanged. Safe to call from both ESM and CJS.
2524
2602
  */
2603
+ function getOptionalChalk() {
2604
+ if (cached !== null) {
2605
+ return cached;
2606
+ }
2607
+ cached = loadChalkSync();
2608
+ return cached;
2609
+ }
2610
+
2525
2611
  /**
2526
2612
  * @class BaseConsolePrettyTransport
2527
2613
  * @description Provides common functionality for "pretty" console transports,
@@ -2532,8 +2618,7 @@ const syntropyLog = SyntropyLog.getInstance();
2532
2618
  class BaseConsolePrettyTransport extends Transport {
2533
2619
  constructor(options) {
2534
2620
  super(options);
2535
- // Chalk v4 is used directly, not instantiated.
2536
- this.chalk = chalk;
2621
+ this.chalk = getOptionalChalk();
2537
2622
  }
2538
2623
  /**
2539
2624
  * The core log method. It handles common logic and delegates specific
@@ -2671,7 +2756,7 @@ class CompactConsoleTransport extends BaseConsolePrettyTransport {
2671
2756
  // Simple stringify for objects/arrays in metadata.
2672
2757
  const formattedValue = typeof value === 'object' && value !== null
2673
2758
  ? JSON.stringify(value)
2674
- : value;
2759
+ : String(value);
2675
2760
  return `${this.chalk.dim(key)}=${this.chalk.gray(formattedValue)}`;
2676
2761
  })
2677
2762
  .join(' ');
@@ -3296,6 +3381,10 @@ class RedisConnectionManager {
3296
3381
  }
3297
3382
  }
3298
3383
  else {
3384
+ // Client never connected or already closed: remove listeners to avoid leak
3385
+ if (typeof this.client.removeAllListeners === 'function') {
3386
+ this.client.removeAllListeners();
3387
+ }
3299
3388
  this.logger.info('Client was not open. Quit operation effectively complete.');
3300
3389
  }
3301
3390
  }
@@ -4537,6 +4626,8 @@ class RedisManager {
4537
4626
  this.logger.info('Closing all Redis connections...');
4538
4627
  const shutdownPromises = Array.from(this.instances.values()).map((instance) => instance.quit());
4539
4628
  await Promise.allSettled(shutdownPromises);
4629
+ this.instances.clear();
4630
+ this.defaultInstance = undefined;
4540
4631
  this.logger.info('All Redis connections have been closed.');
4541
4632
  }
4542
4633
  }