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.
- package/CHANGELOG.md +20 -0
- package/CONTRIBUTING.md +18 -0
- package/README.md +12 -9
- package/dist/index.cjs +106 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +40 -3
- package/dist/index.mjs +105 -15
- package/dist/index.mjs.map +1 -1
- package/dist/logger/transports/BaseConsolePrettyTransport.js +2 -7
- package/dist/logger/transports/BaseConsolePrettyTransport.js.map +1 -1
- package/dist/logger/transports/ColorfulConsoleTransport.js.map +1 -1
- package/dist/logger/transports/CompactConsoleTransport.js +1 -1
- package/dist/logger/transports/CompactConsoleTransport.js.map +1 -1
- package/dist/logger/transports/PrettyConsoleTransport.js.map +1 -1
- package/dist/logger/transports/optionalChalk.js +55 -0
- package/dist/logger/transports/optionalChalk.js.map +1 -0
- package/dist/masking/MaskingEngine.js +44 -9
- package/dist/masking/MaskingEngine.js.map +1 -1
- package/dist/redis/RedisConnectionManager.js +4 -0
- package/dist/redis/RedisConnectionManager.js.map +1 -1
- package/dist/redis/RedisManager.js +2 -0
- package/dist/redis/RedisManager.js.map +1 -1
- package/dist/testing/index.d.ts +15 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/logger/transports/BaseConsolePrettyTransport.d.ts +3 -2
- package/dist/types/logger/transports/ColorfulConsoleTransport.d.ts +3 -3
- package/dist/types/logger/transports/PrettyConsoleTransport.d.ts +3 -3
- package/dist/types/logger/transports/optionalChalk.d.ts +27 -0
- package/dist/types/masking/MaskingEngine.d.ts +15 -1
- 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
|
|
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 |
|
|
83
|
-
| :--- | :--- | :---: | :--- |
|
|
84
|
-
| *(default)* | Plain JSON |
|
|
85
|
-
| `ClassicConsoleTransport` | Structured
|
|
86
|
-
| `PrettyConsoleTransport` | Human-readable pretty
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
226
|
-
return
|
|
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
|
-
|
|
258
|
-
//
|
|
259
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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/
|
|
2523
|
-
* @description
|
|
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
|
-
|
|
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
|
}
|