syntropylog 0.11.0 → 0.11.2

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Docs: README overhaul — full picture table (14 features), init-as-Promise pattern, and a "How" section per feature so users can see what the library does and how to use it. Serialization timeout example set to 100ms (50–100ms recommended). Aligned with doc-es/caracteristicas-y-ejemplos.md.
8
+
9
+ ## 0.11.1
10
+
11
+ ### Patch Changes
12
+
13
+ - Fix: remove duplicate createRequire declaration in ESM bundle. Rollup was injecting an intro that re-declared createRequire already imported by SerializationManager, causing "Identifier 'createRequire' has already been declared" when loading the package in Node ESM (e.g. tsx or "type": "module").
14
+
3
15
  ## 0.11.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -17,657 +17,455 @@
17
17
  <a href="https://github.com/Syntropysoft/SyntropyLog/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/syntropylog.svg" alt="License"></a>
18
18
  <a href="https://github.com/Syntropysoft/SyntropyLog/actions/workflows/ci.yaml"><img src="https://github.com/Syntropysoft/SyntropyLog/actions/workflows/ci.yaml/badge.svg" alt="CI Status"></a>
19
19
  <a href="#"><img src="https://img.shields.io/badge/coverage-95.13%25-brightgreen" alt="Test Coverage"></a>
20
- <a href="#"><img src="https://img.shields.io/badge/status-v0.11.0-brightgreen.svg" alt="Version 0.11.0"></a>
20
+ <a href="#"><img src="https://img.shields.io/badge/status-v0.11.1-brightgreen.svg" alt="Version 0.11.1"></a>
21
21
  <a href="https://socket.dev/npm/package/syntropylog"><img src="https://socket.dev/api/badge/npm/package/syntropylog" alt="Socket Badge"></a>
22
22
  </p>
23
23
 
24
24
  ---
25
25
 
26
- ## 🎯 What is SyntropyLog?
26
+ ## What is SyntropyLog?
27
27
 
28
- SyntropyLog is a **structured observability framework** for Node.js built from scratch, designed around flexibility and control.
28
+ SyntropyLog is a **structured observability framework** for Node.js. You declare what your logs should carry (context, level-based fields, retention, masking), and SyntropyLog makes it happen everywhere—automatically. No manual plumbing, no hidden behavior.
29
29
 
30
- The core idea is simple: **you declare what your logs should carry, and SyntropyLog makes it happen everywhere, automatically.** No manual plumbing, no scattered configuration, no hidden behavior.
31
-
32
- That means:
33
- - A **declarative Logging Matrix** that controls exactly which context fields appear at each log level — lean on `info`, full context on `error`.
34
- - A **fluent logger API** (`withRetention`, `withSource`, `withTransactionId`) that lets you create specialized loggers carrying arbitrary organization-defined metadata.
35
- - **Add, remove, or override transports on demand** — per log call you can send only to specific transports (`.override()`), add extra destinations (`.add()`), or drop one (`.remove()`), without creating new logger instances.
36
- - A **MaskingEngine** that redacts sensitive fields before they reach any transport — built-in strategies and fully custom rules.
37
- - An **Intelligent Serialization Pipeline** that automatically detects and neutralizes circular references, limits object depth, and enforces execution timeouts — making logs immune to application crashes.
38
- - A **UniversalAdapter** that routes logs to any backend (PostgreSQL, MongoDB, Elasticsearch, S3) via a single `executor` function — no coupling, no lock-in.
39
- - A **SanitizationEngine** that strips control characters from all log output, preventing log injection attacks.
40
- - **Lightning Speed**: Optimized core pipeline delivering **~1,000,000 ops/s** even with full masking and context.
41
-
42
- **This is not just another logger. It's about giving your team full declarative control over observability data with industry-leading performance and reliability.**
43
-
44
- ### Built for regulated industries
45
-
46
- SyntropyLog was designed with the constraints of **banking, healthcare, and financial services** in mind:
47
-
48
- - **HIPAA**: Field-level control over what appears in logs at each severity level via the Logging Matrix.
49
- - **SOX**: Immutable audit trail via `withRetention` bindings and dedicated transports.
50
-
51
- ### Tree-shaking friendly
52
-
53
- We ship with `sideEffects: false` and ESM so bundlers (Vite, Rollup, webpack, esbuild) can tree-shake unused code. What ends up in your app is only what you import.
30
+ It is built for **high demand** and **regulated environments** (banking, healthcare, financial services): HIPAA-style field control via the Logging Matrix, SOX-style audit trails via `withRetention`, and a pipeline that never lets logging crash your app.
54
31
 
55
32
  ---
56
33
 
57
- ## 🛡️ Why SyntropyLog? (The Resilience Factor)
58
-
59
- Traditional loggers (and even modern ones) share a common weakness: **serialization is a blocking operation**. If you log a massive, deeply nested, or circular object, the Node.js Event Loop stops. Your API stops responding. Your service might even crash with a `TypeError`.
34
+ ## Full picture what's in the box
35
+
36
+ Everything below is part of the same stack (benchmarks use this full stack). Each item has a **How** section in this README so you can see how to use it.
37
+
38
+ | # | Feature | What it does |
39
+ |---|--------|---------------|
40
+ | 1 | **Native addon (Rust)** | Single-pass serialize + mask + sanitize; ANSI strip. Falls back to JS if unavailable. |
41
+ | 2 | **Logging Matrix** | Declarative control of which context fields appear per level (lean on `info`, full on `error`). |
42
+ | 3 | **Universal Adapter** | Send logs to any backend (PostgreSQL, MongoDB, Elasticsearch, S3) via one `executor` function. |
43
+ | 4 | **MaskingEngine** | Redact sensitive fields before any transport; built-in + custom rules. |
44
+ | 5 | **Serialization pipeline** | Circular refs, depth limit, timeout; logging never blocks the event loop. |
45
+ | 6 | **SanitizationEngine** | Strip control characters; log injection resistant. |
46
+ | 7 | **Context / headers** | Correlation ID and transaction ID from config; single source of truth. |
47
+ | 8 | **Fluent API** | `withRetention`, `withSource`, `withTransactionId` — bind once, carry on every log. |
48
+ | 9 | **Per-call transport control** | `.override()`, `.add()`, `.remove()` for one log call without new logger instances. |
49
+ | 10 | **Audit & retention** | `audit` level (always logged); `withRetention(anyJson)` for compliance routing. |
50
+ | 11 | **Lifecycle** | `init()` / `shutdown()`; graceful flush on SIGTERM/SIGINT. |
51
+ | 12 | **Observability hooks** | `onLogFailure`, `onTransportError`, `onSerializationFallback`, etc.; `isNativeAddonInUse()`. |
52
+ | 13 | **Matrix in runtime** | `reconfigureLoggingMatrix()` without restart; only field visibility, not security. |
53
+ | 14 | **Tree-shaking** | `sideEffects: false` + ESM; bundle only what you import. |
54
+
55
+ **More detail and examples (ES):** [doc-es/caracteristicas-y-ejemplos.md](doc-es/caracteristicas-y-ejemplos.md).
60
56
 
61
- SyntropyLog v0.11.0 introduces the **Log Resilience Engine**, making your application immune to "Death by Log":
57
+ ---
62
58
 
63
- 1. **Event Loop Protection**: Every serialization step is wrapped in a mandatory timeout (default: **50ms**). If serialization takes too long, it is aborted via `Promise.race`, and a safe subset of the data is logged instead. Your app keeps running.
64
- 2. **Circular Reference Immunity**: Built-in hygiene automatically detects and neutralizes circular references. No more `TypeError: Converting circular structure to JSON`.
65
- 3. **Configurable Safety Limits**: Every object is protected by a double-guard:
66
- - `serializationMaxDepth` (default: **10**): Automatically truncates objects deeper than this to prevent Stack Overflow.
67
- - `serializationMaxBreadth` (default: **100**): Limits arrays and object keys to prevent Event Loop blockage on massive structures.
68
- 4. **Silent Observer**: Logging should never throw. Our pipeline catches and reports its own failures inside the log message itself, ensuring 100% reliability.
69
- 5. **Lightning Pipeline**: Consolidation of serialization, masking, and sanitization into a single recursive pass, reaching Pino-level efficiency with Enterprise-level security.
59
+ ## Quick Start
70
60
 
71
- > [!WARNING]
72
- > **Pathological Object Protection**: When an object exceeds the configured Depth or Breadth, it will be automatically truncated (e.g., `[Max Depth reached]` or `[Truncated 500 items]`). This is a feature, not a bug — it ensures your application survives even when buggy code tries to log half the database!
61
+ ### Install
73
62
 
74
- ---
63
+ ```bash
64
+ npm install syntropylog
65
+ ```
75
66
 
76
- ## 📊 Performance Benchmarks
67
+ Prebuilt native addon (Rust) for Linux, Windows, macOS installs automatically on Node ≥20. If unavailable, the JS pipeline is used transparently.
77
68
 
78
- Tested with **2,000,000 logs** on Node.js 20+ (Nulled I/O).
69
+ ### Init and first log
79
70
 
80
- | Library | Throughput | Avg Latency | Notes |
81
- | :--- | :--- | :--- | :--- |
82
- | **Pino** | ~4.1M ops/s | 0.24 µs | Fastest, no masking by default |
83
- | **SyntropyLog v0.11.0** | **~980k ops/s** | **1.02 µs** | **Secure-by-default (Masking + Context)** |
84
- | **Console.log** | ~1.2M ops/s | 0.83 µs | Baseline Node.js performance |
85
- | **Winston** | ~175k ops/s | 5.70 µs | Traditional legacy logger |
71
+ **Initialization is a Promise that must resolve before you can print logs.** Until it resolves (on the `ready` event), `getLogger()` returns a no-op logger that drops all messages. Listen for `ready` and `error` *before* calling `init()`.
86
72
 
87
- > SyntropyLog is **5.5x faster than Winston** and only ~20% slower than pure console while providing deep-object masking and context management.
73
+ ```typescript
74
+ import { syntropyLog } from 'syntropylog';
88
75
 
89
- To compare native addon vs JS-only pipeline: run `pnpm run bench` (with addon) and `SYNTROPYLOG_NATIVE_DISABLE=1 pnpm run bench` (JS only). The benchmark reports "native addon (Rust): yes/no" at startup.
76
+ async function initializeSyntropyLog() {
77
+ return new Promise<void>((resolve, reject) => {
78
+ syntropyLog.on('ready', () => resolve());
79
+ syntropyLog.on('error', (err) => reject(err));
80
+ syntropyLog.init({
81
+ logger: { level: 'info', serviceName: 'my-app' },
82
+ });
83
+ });
84
+ }
90
85
 
91
- ---
86
+ async function main() {
87
+ await initializeSyntropyLog(); // promise must resolve before any log
88
+ const log = syntropyLog.getLogger();
89
+ log.info('Hello, SyntropyLog.');
90
+ }
91
+ main();
92
+ ```
92
93
 
93
- ## 🚀 Quick Start (60 seconds)
94
+ ### Graceful shutdown
94
95
 
95
- ### **1. Install**
96
- ```bash
97
- npm install syntropylog
96
+ ```typescript
97
+ process.on('SIGTERM', async () => {
98
+ await syntropyLog.shutdown();
99
+ process.exit(0);
100
+ });
101
+ process.on('SIGINT', async () => {
102
+ await syntropyLog.shutdown();
103
+ process.exit(0);
104
+ });
98
105
  ```
99
106
 
100
- For the best performance, the package includes **prebuilt native addon binaries (Rust)** for Linux, Windows, and macOS; they install automatically on supported platforms. If the addon is unavailable (e.g. unsupported Node version or platform), the framework falls back to the JS pipeline transparently.
107
+ ### Framework entry points
108
+
109
+ | Framework | Where to call `await init` |
110
+ |----------|----------------------------|
111
+ | Express / Fastify | Before `app.listen()` in server entry |
112
+ | NestJS | `AppModule.onModuleInit()` or before `app.listen()` in `bootstrap()` |
113
+ | Lambda / Serverless | Module-level lazy singleton (outside the handler) |
101
114
 
102
- ### **Available Console Transports**
115
+ ---
103
116
 
104
- By default, SyntropyLog outputs **lightweight plain JSON to the console — automatically, with no configuration needed**. No imports, no setup, no extra dependencies.
117
+ ## 1. Native addon (Rust)
105
118
 
106
- If you want **colored, human-readable output** for development, use one of the pretty console transports. Colours use **built-in ANSI codes** (no chalk or extra dependency). When stdout is a TTY you get colours; when piping or in CI, or if `NO_COLOR` is set, the same format is shown in plain text.
119
+ **What:** Optional Rust addon does serialize + mask + sanitize in one pass. Used automatically when available; no config. Disable with `SYNTROPYLOG_NATIVE_DISABLE=1` (e.g. debugging).
107
120
 
108
- | Transport | Style | Recommended for |
109
- | :--- | :--- | :--- |
110
- | *(default)* | Plain JSON | Production / log aggregators |
111
- | `ClassicConsoleTransport` | Structured single-line, colored | Development |
112
- | `PrettyConsoleTransport` | Human-readable pretty, colored | Development / debugging |
113
- | `CompactConsoleTransport` | Compact one-liner, colored | Development |
114
- | `ColorfulConsoleTransport` | Full-line colored | Development |
121
+ **How:** Nothing to configure. Check at runtime:
115
122
 
116
123
  ```typescript
117
- // Default — no import needed, works out of the box
118
- syntropyLog.init({ logger: { level: 'info', serviceName: 'my-app' } });
124
+ if (syntropyLog.isNativeAddonInUse()) {
125
+ // Rust pipeline active
126
+ }
127
+ ```
119
128
 
120
- // Pretty + colors (built-in ANSI; no extra deps)
121
- import { ClassicConsoleTransport } from 'syntropylog';
129
+ See [doc-es/building-native-addon.es.md](doc-es/building-native-addon.es.md) for building from source.
122
130
 
123
- syntropyLog.init({
124
- logger: {
125
- level: 'info',
126
- serviceName: 'my-app',
127
- transports: [new ClassicConsoleTransport()],
128
- },
129
- });
130
- ```
131
+ ---
131
132
 
132
- ### **Transport pool and per-environment routing**
133
+ ## 2. Logging Matrix
133
134
 
134
- You can define a **named pool of transports** (`transportList`) and choose **per environment** which ones are active (`env`). For a single log call you can override, add, or remove destinations with `.override()`, `.add()`, and `.remove()`.
135
+ **What:** A JSON contract that defines exactly which context fields appear at each log level. If a field isn’t in the matrix for that level, it never appears in the output.
135
136
 
136
- ```typescript
137
- import { syntropyLog, ColorfulConsoleTransport, AdapterTransport, UniversalAdapter } from 'syntropylog';
138
-
139
- const mockToConsole = (label: string) =>
140
- new AdapterTransport({
141
- name: label,
142
- adapter: new UniversalAdapter({
143
- executor: (data) => console.log(`[${label}]`, JSON.stringify(data)),
144
- }),
145
- });
137
+ **How:** Set `loggingMatrix` in `init()`:
146
138
 
139
+ ```typescript
147
140
  await syntropyLog.init({
148
- logger: {
149
- envKey: 'NODE_ENV',
150
- transportList: {
151
- consola: new ColorfulConsoleTransport({ name: 'consola' }),
152
- db: mockToConsole('db'),
153
- azure: mockToConsole('azure'),
154
- archivo: mockToConsole('archivo'),
155
- },
156
- env: {
157
- development: ['consola'],
158
- staging: ['consola', 'archivo', 'azure'],
159
- production: ['consola', 'db', 'azure'],
160
- },
141
+ logger: { level: 'info', serviceName: 'my-app' },
142
+ loggingMatrix: {
143
+ default: ['correlationId'],
144
+ info: ['correlationId', 'userId', 'operation'],
145
+ warn: ['correlationId', 'userId', 'operation', 'errorCode'],
146
+ error: ['correlationId', 'userId', 'operation', 'errorCode', 'tenantId', 'orderId'],
147
+ fatal: ['*'], // all context fields
161
148
  },
162
- redis: { instances: [] },
163
149
  });
164
-
165
- const log = syntropyLog.getLogger('app');
166
- log.info('default according to env');
167
- log.override('consola').info('only to console');
168
- log.remove('db').add('archivo').info('default minus db, plus file');
169
150
  ```
170
151
 
171
- **Full guide and runnable example:** see [examples/TRANSPORT_POOL_AND_ENV.md](examples/TRANSPORT_POOL_AND_ENV.md) and run `examples/TransportPoolExample.ts` to see it in action.
152
+ | Matrix key | Resolves to context |
153
+ |------------|---------------------|
154
+ | `correlationId` | `x-correlation-id`, `correlationId` |
155
+ | `transactionId` | `x-trace-id`, `transactionId` |
156
+ | `userId`, `tenantId`, `operation`, `errorCode`, `orderId`, `paymentId`, `eventType` | same key |
157
+ | `*` | All context fields |
172
158
 
173
159
  ---
174
160
 
175
- ```typescript
176
- import { syntropyLog } from 'syntropylog';
177
- import { ClassicConsoleTransport } from 'syntropylog';
161
+ ## 3. Universal Adapter — log to any backend
178
162
 
179
- const config = {
180
- logger: {
181
- level: 'info',
182
- serviceName: 'ecommerce-app',
183
- transports: [new ClassicConsoleTransport()],
184
- },
185
- context: {
186
- correlationIdHeader: 'X-Correlation-ID', // defines the header name used everywhere
187
- },
188
- };
163
+ **What:** Send each log to PostgreSQL, MongoDB, Elasticsearch, S3, etc. by implementing a single `executor`. No vendor lock-in; you receive one structured object per log (already masked).
189
164
 
190
- // Always wait for the 'ready' event — see the Critical section below
191
- async function initializeSyntropyLog() {
192
- return new Promise<void>((resolve, reject) => {
193
- syntropyLog.on('ready', () => resolve());
194
- syntropyLog.on('error', (err) => reject(err));
195
- syntropyLog.init(config);
196
- });
197
- }
198
-
199
- await initializeSyntropyLog();
165
+ **How:** Use `AdapterTransport` + `UniversalAdapter`:
200
166
 
201
- // Safe to use from here on
202
- const logger = syntropyLog.getLogger();
203
- logger.info('System is clean and ready.');
167
+ ```typescript
168
+ import { AdapterTransport, UniversalAdapter } from 'syntropylog';
169
+
170
+ const dbTransport = new AdapterTransport({
171
+ name: 'db',
172
+ adapter: new UniversalAdapter({
173
+ executor: async (logEntry) => {
174
+ await prisma.systemLog.create({
175
+ data: {
176
+ level: logEntry.level,
177
+ message: logEntry.message,
178
+ serviceName: logEntry.serviceName,
179
+ correlationId: logEntry.correlationId,
180
+ payload: logEntry.meta,
181
+ timestamp: new Date(logEntry.timestamp),
182
+ },
183
+ });
184
+ },
185
+ }),
186
+ });
204
187
  ```
205
188
 
206
- ### **Optional error and fallback hooks**
189
+ `logEntry` has: `level`, `message`, `serviceName`, `correlationId`, `timestamp`, `meta`. If `executor` throws, SyntropyLog logs the error and continues (Silent Observer).
207
190
 
208
- You can pass optional callbacks in the config for observability (logging never throws; these hooks let you observe failures):
191
+ ---
209
192
 
210
- | Hook | When it is called |
211
- |------|--------------------|
212
- | `onLogFailure?(error, entry)` | When a log call fails (serialization or transport error). |
213
- | `onTransportError?(error, context)` | When a transport fails (flush, shutdown, or log write). `context` is `'flush'`, `'shutdown'`, or `'log'`. |
214
- | `onSerializationFallback?(reason)` | When the native addon is used but fails for a call and the framework falls back to the JS pipeline. |
215
- | `onStepError?(step, error)` | When a pipeline step fails (e.g. hygiene). |
216
- | `masking.onMaskingError?(error)` | When masking fails (e.g. timeout); never receives raw payload. |
193
+ ## 4. MaskingEngine
217
194
 
218
- Use `onSerializationFallback` to detect when the native addon failed for a given call and the JS pipeline was used instead (e.g. for metrics or alerting). You can also call `syntropyLog.isNativeAddonInUse()` at runtime to check if the Rust addon is loaded.
195
+ **What:** Redacts sensitive fields before logs reach any transport. Built-in rules (password, email, token, card, SSN, phone) plus custom rules by name/regex.
219
196
 
220
- Example: `syntropyLog.init({ onLogFailure: (err) => metrics.increment('log_failures'), ... });`
197
+ **How:** Configure `masking` in `init()`:
221
198
 
222
- ### **3. Graceful Shutdown (Essential)**
223
199
  ```typescript
224
- async function gracefulShutdown() {
225
- await syntropyLog.shutdown();
226
- }
200
+ import { MaskingStrategy } from 'syntropylog';
227
201
 
228
- process.on('SIGTERM', async () => { await gracefulShutdown(); process.exit(0); });
229
- process.on('SIGINT', async () => { await gracefulShutdown(); process.exit(0); });
202
+ await syntropyLog.init({
203
+ logger: { ... },
204
+ masking: {
205
+ enableDefaultRules: true,
206
+ maskChar: '*',
207
+ preserveLength: true,
208
+ rules: [
209
+ {
210
+ pattern: /cuit|cuil/i,
211
+ strategy: MaskingStrategy.CUSTOM,
212
+ customMask: (value) => value.replace(/\d(?=\d{4})/g, '*'),
213
+ },
214
+ ],
215
+ },
216
+ });
230
217
  ```
231
218
 
232
- ---
233
-
234
- ## ⚠️ Critical: Await Initialization Before Use
219
+ | Strategy | Example output |
220
+ |----------|----------------|
221
+ | PASSWORD | `********` |
222
+ | EMAIL | `j***@example.com` |
223
+ | TOKEN | `eyJh...a1B9c` |
224
+ | CREDIT_CARD | `****-****-****-1234` |
235
225
 
236
- > **SyntropyLog MUST be fully initialized before any call to `getLogger()`, `getRedis()`, or any other resource.** Calling these methods before initialization completes will silently produce a no-op logger that **drops all messages**.
226
+ If masking fails, the pipeline does not throw (Silent Observer). Custom rules: use ReDoS-safe regex; keys longer than 256 chars are skipped.
237
227
 
238
- ### Why this matters
239
-
240
- `syntropyLog.init()` bootstraps the internal pipeline, connects managed resources (Redis, brokers), and wires up serialization and masking layers asynchronously. Until the `ready` event fires, there is no active logger.
241
-
242
- ### ❌ Anti-pattern — fire-and-forget init
228
+ ---
243
229
 
244
- ```typescript
245
- syntropyLog.init(config); // ← not awaited
230
+ ## 5. Serialization pipeline (resilience)
246
231
 
247
- const logger = syntropyLog.getLogger();
248
- logger.info('This message is silently dropped.'); // ← init not complete yet
249
- ```
232
+ **What:** Prevents "death by log": circular refs are neutralized, depth is limited (default 10 → `[MAX_DEPTH_REACHED]`), and a configurable timeout via `serializerTimeoutMs` aborts long serialization so the event loop keeps running. For most apps 50–100ms is enough; the library default is higher.
250
233
 
251
- ### Correct pattern event-based initialization
234
+ **How:** Set timeout in logger config (e.g. 50–100ms):
252
235
 
253
236
  ```typescript
254
- async function initializeSyntropyLog() {
255
- return new Promise<void>((resolve, reject) => {
256
- syntropyLog.on('ready', () => resolve()); // wait for this
257
- syntropyLog.on('error', (err) => reject(err));
258
- syntropyLog.init(config);
259
- });
237
+ logger: {
238
+ serviceName: 'my-app',
239
+ serializerTimeoutMs: 100, // optional; e.g. 50–100ms for most apps
260
240
  }
261
-
262
- await initializeSyntropyLog(); // ← nothing runs before this resolves
263
- const logger = syntropyLog.getLogger();
264
- logger.info('System initialized and ready.');
265
241
  ```
266
242
 
267
- ### Framework entry points
268
-
269
- | Framework | Bootstrap location |
270
- | :--- | :--- |
271
- | **Express / Fastify** | Call `await initializeSyntropyLog()` before `app.listen()` in `server.ts` / `main.ts` |
272
- | **NestJS** | Inside `AppModule.onModuleInit()` or in `bootstrap()` before `app.listen()` |
273
- | **Lambda / Serverless** | Module-level block (outside the handler), using a lazy singleton pattern |
243
+ Logging never throws; failures are reported inside the log payload.
274
244
 
275
245
  ---
276
246
 
277
- ## 🔗 Context: Automatic Correlation ID Propagation
247
+ ## 6. SanitizationEngine
278
248
 
279
- > Context management is powered by Node.js `AsyncLocalStorage`. Context is **not global** — it only exists inside `contextManager.run()` blocks. In production, SyntropyLog's adapters create it automatically for every request.
249
+ **What:** Strips control characters and ANSI from string values before any transport writes. Reduces log injection risk.
280
250
 
281
- ### How it works
251
+ **How:** No configuration; it runs inside the pipeline. Together with the Logging Matrix (whitelist), it forms the safety boundary for log content.
282
252
 
283
- The `correlationIdHeader` you set in `context` config is the **single source of truth** — it's the header name read from incoming requests and propagated to all outgoing calls and logs.
253
+ ---
284
254
 
285
- ```typescript
286
- // Defined once in init()
287
- context: {
288
- correlationIdHeader: 'X-Correlation-ID',
289
- }
290
- ```
255
+ ## 7. Context — correlation ID and transaction ID
291
256
 
292
- ### The Context Wrapper
257
+ **What:** One config defines header names; correlation and transaction IDs propagate to all logs and operations inside the same context (e.g. one request).
293
258
 
294
- ```typescript
295
- // ❌ WITHOUT context — no correlationId in logs
296
- logger.info('User logged in');
259
+ **How:** Set `context` in `init()` and use a small middleware:
297
260
 
298
- // ✅ WITH context — correlationId flows automatically to every log
299
- await contextManager.run(async () => {
300
- logger.info('User logged in'); // correlationId attached automatically
261
+ ```typescript
262
+ await syntropyLog.init({
263
+ context: {
264
+ correlationIdHeader: 'X-Correlation-ID',
265
+ transactionIdHeader: 'X-Transaction-ID',
266
+ },
301
267
  });
302
- ```
303
268
 
304
- ### 🔮 The Magic Middleware (2 Lines of Code)
305
-
306
- Add this **once** to your Express/Fastify app and never think about Correlation IDs again:
307
-
308
- ```typescript
269
+ // Express/Fastify middleware (once per app)
270
+ const { contextManager } = syntropyLog;
309
271
  app.use(async (req, res, next) => {
310
272
  await contextManager.run(async () => {
311
- // 🎯 MAGIC: Just 2 lines!
312
- const correlationId = contextManager.getCorrelationId(); // Detects incoming or generates new
313
- contextManager.set(contextManager.getCorrelationIdHeaderName(), correlationId); // Sets in context
314
-
273
+ const correlationId = contextManager.getCorrelationId();
274
+ contextManager.set(contextManager.getCorrelationIdHeaderName(), correlationId);
315
275
  next();
316
276
  });
317
277
  });
318
278
  ```
319
279
 
320
- Why this is marvelous:
321
-
322
- - **Intelligent Detection**: `getCorrelationId()` uses the existing ID from the incoming request or generates a new one
323
- - **Automatic Configuration**: `getCorrelationIdHeaderName()` reads your `context.correlationIdHeader` config — change it once, updated everywhere
324
- - **Automatic Propagation**: Once set, it propagates to all logs and operations
325
-
326
- ### In Real Applications
327
-
328
- In production, context is created automatically by SyntropyLog's built-in adapters (no manual `run()` needed):
280
+ After that, every `logger.info(...)` inside the request carries the same `correlationId` without passing it manually.
329
281
 
330
- - HTTP middleware (Express, Fastify, Koa)
331
- - Message queue handlers (Kafka, RabbitMQ)
332
- - Background job processors / API gateways
282
+ ---
333
283
 
334
- | Scenario | Pattern |
335
- | :--- | :--- |
336
- | **Examples & quick tests** | Wrap logging in `contextManager.run()` manually |
337
- | **Production apps** | Use SyntropyLog's HTTP/broker adapters — context is automatic |
284
+ ## 8. Fluent API — withRetention, withSource, withTransactionId
338
285
 
339
- ---
286
+ **What:** Builders that return new loggers with bound metadata: `withSource('ModuleName')`, `withTransactionId('txn-123')`, `withRetention({ policy: 'SOX', years: 5 })`. Every log from that logger carries that data.
340
287
 
341
- ## 🎯 What Your Logs Look Like
288
+ **How:**
342
289
 
343
- The **Logging Matrix** lets you control exactly how much data appears per log level — lean on success, rich on failure:
290
+ ```typescript
291
+ const log = syntropyLog.getLogger();
344
292
 
345
- **INFO (success path)** — minimal cost:
346
- ```
347
- 12:56:00 [INFO] (ecommerce-app): User request processed { status: 'completed', duration: '150ms' }
348
- { "userId": 123, "operation": "user-login" }
349
- ```
293
+ const auditLogger = log
294
+ .withSource('PaymentService')
295
+ .withRetention({ policy: 'SOX_AUDIT_TRAIL', years: 5 });
350
296
 
351
- **ERROR (failure path)** full context for debugging:
352
- ```
353
- 12:56:00 [ERROR] (ecommerce-app): User request processed { status: 'completed', duration: '150ms' }
354
- {
355
- "userId": 123, "email": "user@example.com", "password": "***MASKED***",
356
- "firstName": "John", "ipAddress": "127.0.0.1",
357
- "sessionId": "sess-789", "requestId": "req-456"
358
- }
297
+ auditLogger.info({ userId: 123, action: 'payment' }, 'Payment processed');
298
+ // Entry includes source and retention; your executor can route by retention.policy
359
299
  ```
360
300
 
361
- > Same log call, different information per level. Success logs are lean and cheap; error logs have all the context you need to debug — with sensitive fields always masked.
301
+ | Builder | Binds |
302
+ |---------|--------|
303
+ | `withSource('X')` | `source: 'X'` |
304
+ | `withTransactionId('id')` | `transactionId: 'id'` |
305
+ | `withRetention({ ... })` | `retention: { ... }` (any JSON) |
306
+ | `child({ k: v })` | arbitrary key-value |
362
307
 
363
308
  ---
364
309
 
365
- ## 📋 Logging Matrix — Declarative Field Control
310
+ ## 9. Per-call transport control
366
311
 
367
- The **Logging Matrix** is a JSON contract that defines *exactly* which context fields appear in each log level. It's lightweight, powerful, and provides a strong security guarantee: **if a field isn't in the matrix, it can't appear in the log output — no matter what's in the context.**
312
+ **What:** For a single log call you can send only to specific transports (`.override()`), add destinations (`.add()`), or remove one (`.remove()`), without creating new logger instances.
368
313
 
369
- ### Why this matters
370
-
371
- Without it, any field stored in the `AsyncLocalStorage` context would flow into every log. With it, you have an explicit whitelist per level:
314
+ **How:** Define a transport pool with `transportList` and `env`, then use the fluent methods on the next call only:
372
315
 
373
316
  ```typescript
374
317
  await syntropyLog.init({
375
- logger: { ... },
376
- context: { ... },
377
- loggingMatrix: {
378
- // Always include these in every level (unless overridden below)
379
- default: ['correlationId'],
380
-
381
- // info: lean — just what you need to understand the happy path
382
- info: ['correlationId', 'userId', 'operation'],
383
-
384
- // warn: a bit more context to understand what triggered the warning
385
- warn: ['correlationId', 'userId', 'operation', 'errorCode'],
386
-
387
- // error/fatal: everything — full context to debug the failure
388
- error: ['correlationId', 'userId', 'operation', 'errorCode', 'tenantId', 'orderId'],
389
- fatal: ['*'], // wildcard: include ALL context fields
318
+ logger: {
319
+ envKey: 'NODE_ENV',
320
+ transportList: {
321
+ consola: new ColorfulConsoleTransport({ name: 'consola' }),
322
+ db: dbTransport,
323
+ azure: azureTransport,
324
+ },
325
+ env: {
326
+ development: ['consola'],
327
+ production: ['consola', 'db', 'azure'],
328
+ },
390
329
  },
391
330
  });
392
- ```
393
-
394
- Available named fields mapped by the engine:
395
331
 
396
- | Matrix key | Context key(s) it resolves |
397
- | :--- | :--- |
398
- | `correlationId` | `x-correlation-id`, `correlationId` |
399
- | `transactionId` | `x-trace-id`, `transactionId` |
400
- | `userId` | `userId` |
401
- | `tenantId` | `tenantId` |
402
- | `operation` | `operation` |
403
- | `errorCode` | `errorCode` |
404
- | `orderId` | `orderId` |
405
- | `paymentId` | `paymentId` |
406
- | `eventType` | `eventType` |
407
- | `*` | All context fields (wildcard for `debug`/`fatal`) |
332
+ const log = syntropyLog.getLogger('app');
333
+ log.info('uses env default');
334
+ log.override('consola').info('only to console');
335
+ log.remove('db').add('azure').info('default minus db, plus azure');
336
+ ```
408
337
 
409
- ### 🔒 Injection Safety
338
+ See [examples/TRANSPORT_POOL_AND_ENV.md](examples/TRANSPORT_POOL_AND_ENV.md) and `examples/TransportPoolExample.ts`.
410
339
 
411
- The `SanitizationEngine` runs **before** any transport writes a log entry. It strips ANSI escape codes and control characters from every string value — making log injection attacks against terminal or SIEM viewers impossible.
340
+ ---
412
341
 
413
- Two-layer protection:
414
- 1. **Logging Matrix** — whitelist filter: only declared fields pass through
415
- 2. **SanitizationEngine** — strips control characters from string values before output
342
+ ## 10. Audit and retention
416
343
 
417
- ### Dynamic reconfiguration
344
+ **What:** The `audit` level is always logged regardless of the configured level. `withRetention(anyJson)` attaches policy metadata (e.g. GDPR, SOX, PCI-DSS) so your `executor` can route to different tables or buckets.
418
345
 
419
- The matrix can be updated at runtime **without restarting** your application. Useful for temporarily increasing verbosity in production:
346
+ **How:**
420
347
 
421
348
  ```typescript
422
- const { contextManager } = syntropyLog;
423
-
424
- // Temporarily enable full context for debug investigation
425
- contextManager.reconfigureLoggingMatrix({
426
- default: ['correlationId'],
427
- info: ['correlationId', 'userId', 'operation'],
428
- error: ['*'], // full context on errors
429
- });
430
-
431
- // Later, restore original config
432
- contextManager.reconfigureLoggingMatrix(originalMatrix);
349
+ const auditLogger = log.withRetention({ policy: 'GDPR_ARTICLE_17', years: 7 });
350
+ auditLogger.audit({ userId: 123, action: 'data-export' }, 'GDPR export');
351
+ // Always written; retention travels in the entry for routing
433
352
  ```
434
353
 
435
- > **Security boundary**: `reconfigureLoggingMatrix()` only changes *which* context fields are visible it cannot alter masking rules, transports, or any security configuration set at `init()` time.
354
+ Your `executor` can read `logEntry.retention?.policy` and persist to the right store.
436
355
 
437
356
  ---
438
357
 
439
- ## 🛡️ Data Masking
440
-
441
- SyntropyLog ships with a **MaskingEngine** that automatically redacts sensitive fields in every log — before they ever reach a transport or database.
358
+ ## 11. Lifecycle — init / shutdown
442
359
 
443
- ### Built-in strategies (enabled by default)
360
+ **What:** `init()` starts the pipeline and emits `ready` when safe to use. `shutdown()` flushes in-flight logs and closes resources; hook it to SIGTERM/SIGINT so you don’t lose logs on exit.
444
361
 
445
- | Strategy | Matched field names (regex) | Example output |
446
- | :--- | :--- | :--- |
447
- | `PASSWORD` | `password`, `pass`, `pwd`, `secret` | `********` |
448
- | `EMAIL` | `email` | `j***@example.com` |
449
- | `TOKEN` | `token`, `api_key`, `auth_token`, `jwt`, `bearer` | `eyJh...a1B9c` |
450
- | `CREDIT_CARD` | `credit_card`, `card_number`, `payment_number` | `****-****-****-1234` |
451
- | `SSN` | `ssn`, `social_security`, `security_number` | `***-**-6789` |
452
- | `PHONE` | `phone`, `phone_number`, `mobile_number` | `***-***-4567` |
453
-
454
- ### Configuration in `init()`
362
+ **How:** Init is shown in Quick Start. Shutdown:
455
363
 
456
364
  ```typescript
457
- import { MaskingStrategy } from 'syntropylog';
458
-
459
- await syntropyLog.init({
460
- logger: { ... },
461
- context: { ... },
462
- masking: {
463
- // Default rules are ON — set to false to start from scratch
464
- enableDefaultRules: true,
465
-
466
- // Global mask character
467
- maskChar: '*',
468
-
469
- // Preserve original field length by default
470
- preserveLength: true,
471
-
472
- // Add your own rules on top of the defaults
473
- rules: [
474
- {
475
- // Mask any field whose name contains 'cuit' or 'cuil'
476
- pattern: /cuit|cuil/i,
477
- strategy: MaskingStrategy.CUSTOM,
478
- customMask: (value) => value.replace(/\d(?=\d{4})/g, '*'), // keep last 4 digits
479
- },
480
- {
481
- // Mask internal API keys
482
- pattern: /internal_key|service_secret/i,
483
- strategy: MaskingStrategy.TOKEN,
484
- },
485
- ],
486
- },
365
+ process.on('SIGTERM', async () => {
366
+ await syntropyLog.shutdown();
367
+ process.exit(0);
487
368
  });
488
369
  ```
489
370
 
490
- > **Silent Observer guarantee**: if the masking engine fails for any reason, it returns the original object and the application keeps running — it never throws.
491
-
492
- **Performance**: Built-in rules use synchronous regex matching (safe, known patterns). Custom rules use the same synchronous engine for maximum speed.
493
-
494
- > ⚠️ **Security Warning (Custom Rules)**
495
- > Node.js regular expressions execute synchronously on the main thread. To prevent Event Loop blocking, SyntropyLog automatically skips evaluating any JSON keys longer than 256 characters. However, when writing **Custom Masking Rules**, it is your responsibility to write optimized, ReDoS-safe regular expressions. A catastrophic backtracking pattern in a custom rule could temporarily freeze your application if triggered by a specifically crafted payload.
496
-
497
371
  ---
498
372
 
499
- ## 💾 Universal Persistence — Log to Any Database
373
+ ## 12. Observability hooks
500
374
 
501
- The `UniversalAdapter` lets you send structured logs to **any storage backend** (PostgreSQL, MongoDB, Elasticsearch, S3, etc.) by providing a single `executor` function. No coupling to any ORM or driver.
375
+ **What:** Optional callbacks to observe failures without logging throwing: `onLogFailure`, `onTransportError`, `onSerializationFallback`, `onStepError`, `masking.onMaskingError`. Plus `isNativeAddonInUse()` at runtime.
502
376
 
503
- ```typescript
504
- import { UniversalAdapter } from 'syntropylog';
505
- import { prisma } from './db'; // your Prisma client, Mongoose model, pg pool, etc.
506
-
507
- const dbTransport = new UniversalAdapter({
508
- executor: async (logEntry) => {
509
- // logEntry is the fully-formed, masked log object
510
- await prisma.systemLog.create({
511
- data: {
512
- level: logEntry.level,
513
- message: logEntry.message,
514
- service: logEntry.serviceName,
515
- correlationId: logEntry.correlationId,
516
- payload: logEntry.meta, // JSON column
517
- timestamp: new Date(logEntry.timestamp),
518
- },
519
- });
520
- },
521
- });
377
+ **How:** Pass them in `init()`:
522
378
 
379
+ ```typescript
523
380
  await syntropyLog.init({
524
- logger: {
525
- serviceName: 'ecommerce-app',
526
- transports: [new ClassicConsoleTransport(), dbTransport], // add alongside console
527
- },
528
- ...
381
+ logger: { ... },
382
+ onLogFailure: (err, entry) => metrics.increment('log_failures'),
383
+ onTransportError: (err, context) => alerting.notify('transport', context, err),
384
+ onSerializationFallback: () => metrics.increment('serialization_fallback'),
385
+ masking: { onMaskingError: (err) => metrics.increment('masking_errors') },
529
386
  });
530
387
  ```
531
388
 
532
- ### How the executor receives data
389
+ | Hook | When it runs |
390
+ |------|----------------|
391
+ | `onLogFailure?(error, entry)` | Log call fails (serialization or transport) |
392
+ | `onTransportError?(error, context)` | Transport fails; `context` is `'flush'`, `'shutdown'`, or `'log'` |
393
+ | `onSerializationFallback?(reason)` | Native addon failed for this call; JS pipeline used |
394
+ | `onStepError?(step, error)` | Pipeline step failed (e.g. hygiene) |
395
+ | `masking.onMaskingError?(error)` | Masking failed (e.g. timeout); never receives raw payload |
533
396
 
534
- The `executor` receives a single structured object — already masked and serialized — with these fields:
535
-
536
- | Field | Type | Description |
537
- | :--- | :--- | :--- |
538
- | `level` | `string` | Log level: `info`, `error`, etc. |
539
- | `message` | `string` | The log message |
540
- | `serviceName` | `string` | From your config |
541
- | `correlationId` | `string` | From the active context |
542
- | `timestamp` | `number` | Unix timestamp |
543
- | `meta` | `object` | Any extra fields passed to the log call — already masked |
544
-
545
- > **Silent Observer**: if your `executor` throws, SyntropyLog logs the error to console and continues — your app never crashes because of a log transport failure.
546
-
547
- ### 🔗 Fluent Logger API — Specialized Loggers
548
-
549
- The `ILogger` interface provides **fluent builders** that create new, immutable child loggers with specific bindings attached. Every log call from that logger will carry those bindings automatically — no passing parameters around.
550
-
551
- | Builder | What it binds | Typical use |
552
- | :--- | :--- | :--- |
553
- | `withSource('ModuleName')` | `source: 'ModuleName'` | Tag logs by module or component |
554
- | `withTransactionId('txn-123')` | `transactionId: 'txn-123'` | Track a business transaction ID |
555
- | `withRetention({ ...anyJson })` | `retention: { ...anyJson }` | Attach org-defined metadata to route logs |
556
- | `child({ key: value })` | Any key-value pairs | General-purpose bindings |
397
+ ---
557
398
 
558
- All builders return a **new logger** — the original is never mutated.
399
+ ## 13. Matrix in runtime
559
400
 
560
- #### `withRetention()` Your JSON, Your Rules
401
+ **What:** Change which context fields are visible per level without restart. Security boundary: only field visibility changes; masking and transports stay as set at `init()`.
561
402
 
562
- `withRetention()` accepts **any JSON** object. SyntropyLog deep-clones it and attaches it as a `retention` field in every log entry — without reading or interpreting its contents. The meaning belongs entirely to your organization.
403
+ **How:**
563
404
 
564
405
  ```typescript
565
- const logger = syntropyLog.getLogger();
566
-
567
- // Each logger carries the metadata your team defined
568
- const complianceLogger = logger.withRetention({
569
- policy: 'GDPR_ARTICLE_17',
570
- years: 7,
571
- immutable: true,
572
- region: 'eu-west-1',
573
- });
574
-
575
- const auditLogger = logger.withRetention({
576
- policy: 'SOX_AUDIT_TRAIL',
577
- years: 5,
406
+ syntropyLog.reconfigureLoggingMatrix({
407
+ default: ['correlationId'],
408
+ info: ['correlationId', 'userId', 'operation'],
409
+ error: ['*'],
578
410
  });
411
+ // Restore later with original matrix
412
+ ```
579
413
 
580
- const debugLogger = logger.withRetention({
581
- policy: 'EPHEMERAL',
582
- days: 7,
583
- tier: 'hot',
584
- });
414
+ ---
585
415
 
586
- // Combine with other fluent builders
587
- const paymentAuditLogger = logger
588
- .withSource('PaymentService')
589
- .withRetention({ policy: 'PCI_DSS', years: 5 });
590
- ```
416
+ ## 14. Tree-shaking
591
417
 
592
- Use them anywhere in your application the binding is already in every log:
418
+ **What:** Package is published with `sideEffects: false` and ESM so bundlers include only what you import.
593
419
 
594
- ```typescript
595
- // In PaymentService.ts
596
- complianceLogger.info({ userId: 123, action: 'data-export' }, 'GDPR event');
420
+ **How:** Import only what you use; unused transports and adapters are dropped from the bundle.
597
421
 
598
- // In AuthModule.ts — completely independent, same pattern
599
- auditLogger.warn({ userId: 456, action: 'login-failed' }, 'Security event');
600
- ```
422
+ ---
601
423
 
602
- Each entry arrives at the transport with `retention` ready to be acted upon:
603
-
604
- ```json
605
- {
606
- "level": "info",
607
- "message": "GDPR event",
608
- "correlationId": "uuid-...",
609
- "source": "PaymentService",
610
- "retention": { "policy": "GDPR_ARTICLE_17", "years": 7, "immutable": true },
611
- "userId": 123,
612
- "action": "data-export"
613
- }
614
- ```
424
+ ## Console transports (default and pretty)
615
425
 
616
- Your executor routes it SyntropyLog has no opinion on the content:
426
+ By default the library outputs **plain JSON** to the console. For colored, human-readable output in development, use a pretty transport:
617
427
 
618
- ```typescript
619
- const dbTransport = new UniversalAdapter({
620
- executor: async (logEntry) => {
621
- const policy = logEntry.retention?.policy;
428
+ | Transport | Style |
429
+ |-----------|--------|
430
+ | *(default)* | Plain JSON |
431
+ | `ClassicConsoleTransport` | Single-line, colored |
432
+ | `PrettyConsoleTransport` | Pretty-printed, colored |
433
+ | `CompactConsoleTransport` | Compact one-liner, colored |
434
+ | `ColorfulConsoleTransport` | Full-line colored |
622
435
 
623
- const destination =
624
- policy === 'GDPR_ARTICLE_17' || policy === 'SOX_AUDIT_TRAIL' ? 'audit_logs'
625
- : policy === 'PCI_DSS' ? 'payment_audit_logs'
626
- : policy === 'EPHEMERAL' ? 'debug_logs'
627
- : 'system_logs';
436
+ Colors use built-in ANSI; no chalk. Disabled when stdout is not a TTY or when `NO_COLOR` is set.
628
437
 
629
- await db[destination].insert(logEntry);
438
+ ```typescript
439
+ import { ClassicConsoleTransport } from 'syntropylog';
440
+ syntropyLog.init({
441
+ logger: {
442
+ level: 'info',
443
+ serviceName: 'my-app',
444
+ transports: [new ClassicConsoleTransport()],
630
445
  },
631
446
  });
632
447
  ```
633
448
 
634
- > **The JSON is yours.** Field names, values, and interpretation belong entirely to your organization. `withRetention()` is just the mechanism to bind it once and have it travel with every log — automatically, safely, immutably.
635
-
636
- ---
637
-
638
- ## ✨ Key Capabilities
639
-
640
- | Feature | Description |
641
- | :--- | :--- |
642
- | **Instance Management** | Register shared resources once, use them everywhere with confidence. |
643
- | **Correlation Tracking** | Trace requests across multiple services and DBs automatically. |
644
- | **Silent Observer** | If logging fails, your application keeps running perfectly. |
645
- | **Universal Persistence** | Map logs to ANY database (SQL/NoSQL) with pure JSON mapping. |
646
-
647
- ---
648
-
649
- ---
650
-
651
449
  ---
652
450
 
653
- ## 🔒 Security & Compliance
451
+ ## Security & Compliance
654
452
 
655
- | | Dynamically configurable | Immutable |
656
- | :--- | :--- | :--- |
657
- | ✅ **Safe to change** | Logging Matrix, Log Level, additive Masking Fields | |
658
- | 🔒 **Fixed at init** | — | Transports, core masking config (`maskChar`, `maxDepth`), Redis/HTTP/broker infrastructure |
453
+ | Dynamically configurable | Fixed at init |
454
+ |--------------------------|---------------|
455
+ | Logging Matrix, log level, additive masking rules | Transports, core masking config (`maskChar`, `maxDepth`), Redis/HTTP/broker |
659
456
 
660
457
  ---
661
458
 
662
- ## 📚 Documentation
459
+ ## Documentation
663
460
 
664
- - **[Improvement plan & roadmap](docs/code-improvement-analysis-and-plan.md)** — Code analysis, prioritized backlog, and phased work plan.
665
- - **[Rust addon implementation plan](doc-es/rust-implementation-plan.md)** (ES) Phased checklist to maximize use of the native addon (“Formula 1” path); links to [rust-pipeline-optimization.md](doc-es/rust-pipeline-optimization.md) for details.
666
- - **Benchmarks** — Summary in the [Performance Benchmarks](#-performance-benchmarks) section above; run `pnpm run bench` or `pnpm run bench:memory` from the repo root. [Benchmark run report (throughput + memory + high-demand stack)](docs/benchmark-memory-run.md) (EN) · [Informe de ejecución (ES)](doc-es/benchmark-memory-run.md). With the optional Rust addon built (`cd syntropylog-native && pnpm run build`), the benchmark reports native addon usage.
461
+ - **[Features and examples (ES)](doc-es/caracteristicas-y-ejemplos.md)** — Canonical stack list with explanations and code examples; aligned with the benchmark report.
462
+ - **[Benchmark report (throughput + memory)](doc-es/benchmark-memory-run.md)** — Run `pnpm run bench` or `pnpm run bench:memory` from repo root. Compare native vs JS: `pnpm run bench` vs `SYNTROPYLOG_NATIVE_DISABLE=1 pnpm run bench`.
463
+ - **[Rust addon (ES)](doc-es/building-native-addon.es.md)** Build from source.
464
+ - **[Improvement plan & roadmap](doc-es/code-improvement-analysis-and-plan.md)** — Backlog and phased plan.
465
+ - **[Rust implementation plan (ES)](doc-es/rust-implementation-plan.md)** — Native addon checklist; links to [rust-pipeline-optimization.md](doc-es/rust-pipeline-optimization.md).
667
466
 
668
467
  ---
669
468
 
670
- ## 🤝 Contributing & License
469
+ ## Contributing & License
671
470
 
672
- We love contributors! Check our [Contributing Guide](./CONTRIBUTING.md).
673
- Project licensed under **Apache-2.0**.
471
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). License: **Apache-2.0**.
package/dist/index.mjs CHANGED
@@ -1,7 +1,3 @@
1
- import { createRequire } from 'node:module';
2
- const require = createRequire(import.meta.url);
3
-
4
-
5
1
  import { EventEmitter } from 'events';
6
2
  import { AsyncLocalStorage } from 'node:async_hooks';
7
3
  import { randomUUID } from 'crypto';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "syntropylog",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "engines": {
5
5
  "node": ">=20.0.0"
6
6
  },