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 +12 -0
- package/README.md +285 -487
- package/dist/index.mjs +0 -4
- package/package.json +1 -1
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.
|
|
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
|
-
##
|
|
26
|
+
## What is SyntropyLog?
|
|
27
27
|
|
|
28
|
-
SyntropyLog is a **structured observability framework** for Node.js
|
|
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
|
-
|
|
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
|
-
##
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
57
|
+
---
|
|
62
58
|
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
+
### Init and first log
|
|
79
70
|
|
|
80
|
-
|
|
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
|
-
|
|
73
|
+
```typescript
|
|
74
|
+
import { syntropyLog } from 'syntropylog';
|
|
88
75
|
|
|
89
|
-
|
|
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
|
-
|
|
94
|
+
### Graceful shutdown
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
+
---
|
|
103
116
|
|
|
104
|
-
|
|
117
|
+
## 1. Native addon (Rust)
|
|
105
118
|
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
124
|
+
if (syntropyLog.isNativeAddonInUse()) {
|
|
125
|
+
// Rust pipeline active
|
|
126
|
+
}
|
|
127
|
+
```
|
|
119
128
|
|
|
120
|
-
|
|
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
|
-
|
|
124
|
-
logger: {
|
|
125
|
-
level: 'info',
|
|
126
|
-
serviceName: 'my-app',
|
|
127
|
-
transports: [new ClassicConsoleTransport()],
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
```
|
|
131
|
+
---
|
|
131
132
|
|
|
132
|
-
|
|
133
|
+
## 2. Logging Matrix
|
|
133
134
|
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
-
import { syntropyLog } from 'syntropylog';
|
|
177
|
-
import { ClassicConsoleTransport } from 'syntropylog';
|
|
161
|
+
## 3. Universal Adapter — log to any backend
|
|
178
162
|
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
189
|
+
`logEntry` has: `level`, `message`, `serviceName`, `correlationId`, `timestamp`, `meta`. If `executor` throws, SyntropyLog logs the error and continues (Silent Observer).
|
|
207
190
|
|
|
208
|
-
|
|
191
|
+
---
|
|
209
192
|
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
197
|
+
**How:** Configure `masking` in `init()`:
|
|
221
198
|
|
|
222
|
-
### **3. Graceful Shutdown (Essential)**
|
|
223
199
|
```typescript
|
|
224
|
-
|
|
225
|
-
await syntropyLog.shutdown();
|
|
226
|
-
}
|
|
200
|
+
import { MaskingStrategy } from 'syntropylog';
|
|
227
201
|
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
219
|
+
| Strategy | Example output |
|
|
220
|
+
|----------|----------------|
|
|
221
|
+
| PASSWORD | `********` |
|
|
222
|
+
| EMAIL | `j***@example.com` |
|
|
223
|
+
| TOKEN | `eyJh...a1B9c` |
|
|
224
|
+
| CREDIT_CARD | `****-****-****-1234` |
|
|
235
225
|
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
syntropyLog.init(config); // ← not awaited
|
|
230
|
+
## 5. Serialization pipeline (resilience)
|
|
246
231
|
|
|
247
|
-
|
|
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
|
-
|
|
234
|
+
**How:** Set timeout in logger config (e.g. 50–100ms):
|
|
252
235
|
|
|
253
236
|
```typescript
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
247
|
+
## 6. SanitizationEngine
|
|
278
248
|
|
|
279
|
-
|
|
249
|
+
**What:** Strips control characters and ANSI from string values before any transport writes. Reduces log injection risk.
|
|
280
250
|
|
|
281
|
-
|
|
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
|
-
|
|
253
|
+
---
|
|
284
254
|
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
299
|
-
await
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
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
|
-
|
|
331
|
-
- Message queue handlers (Kafka, RabbitMQ)
|
|
332
|
-
- Background job processors / API gateways
|
|
282
|
+
---
|
|
333
283
|
|
|
334
|
-
|
|
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
|
-
|
|
288
|
+
**How:**
|
|
342
289
|
|
|
343
|
-
|
|
290
|
+
```typescript
|
|
291
|
+
const log = syntropyLog.getLogger();
|
|
344
292
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
310
|
+
## 9. Per-call transport control
|
|
366
311
|
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
338
|
+
See [examples/TRANSPORT_POOL_AND_ENV.md](examples/TRANSPORT_POOL_AND_ENV.md) and `examples/TransportPoolExample.ts`.
|
|
410
339
|
|
|
411
|
-
|
|
340
|
+
---
|
|
412
341
|
|
|
413
|
-
|
|
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
|
-
|
|
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
|
-
|
|
346
|
+
**How:**
|
|
420
347
|
|
|
421
348
|
```typescript
|
|
422
|
-
const {
|
|
423
|
-
|
|
424
|
-
//
|
|
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
|
-
|
|
354
|
+
Your `executor` can read `logEntry.retention?.policy` and persist to the right store.
|
|
436
355
|
|
|
437
356
|
---
|
|
438
357
|
|
|
439
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
##
|
|
373
|
+
## 12. Observability hooks
|
|
500
374
|
|
|
501
|
-
|
|
375
|
+
**What:** Optional callbacks to observe failures without logging throwing: `onLogFailure`, `onTransportError`, `onSerializationFallback`, `onStepError`, `masking.onMaskingError`. Plus `isNativeAddonInUse()` at runtime.
|
|
502
376
|
|
|
503
|
-
|
|
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
|
-
|
|
526
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
399
|
+
## 13. Matrix in runtime
|
|
559
400
|
|
|
560
|
-
|
|
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
|
-
|
|
403
|
+
**How:**
|
|
563
404
|
|
|
564
405
|
```typescript
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
581
|
-
policy: 'EPHEMERAL',
|
|
582
|
-
days: 7,
|
|
583
|
-
tier: 'hot',
|
|
584
|
-
});
|
|
414
|
+
---
|
|
585
415
|
|
|
586
|
-
|
|
587
|
-
const paymentAuditLogger = logger
|
|
588
|
-
.withSource('PaymentService')
|
|
589
|
-
.withRetention({ policy: 'PCI_DSS', years: 5 });
|
|
590
|
-
```
|
|
416
|
+
## 14. Tree-shaking
|
|
591
417
|
|
|
592
|
-
|
|
418
|
+
**What:** Package is published with `sideEffects: false` and ESM so bundlers include only what you import.
|
|
593
419
|
|
|
594
|
-
|
|
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
|
-
|
|
599
|
-
auditLogger.warn({ userId: 456, action: 'login-failed' }, 'Security event');
|
|
600
|
-
```
|
|
422
|
+
---
|
|
601
423
|
|
|
602
|
-
|
|
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
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
451
|
+
## Security & Compliance
|
|
654
452
|
|
|
655
|
-
|
|
|
656
|
-
|
|
657
|
-
|
|
|
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
|
-
##
|
|
459
|
+
## Documentation
|
|
663
460
|
|
|
664
|
-
- **[
|
|
665
|
-
- **[
|
|
666
|
-
- **
|
|
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
|
-
##
|
|
469
|
+
## Contributing & License
|
|
671
470
|
|
|
672
|
-
|
|
673
|
-
Project licensed under **Apache-2.0**.
|
|
471
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). License: **Apache-2.0**.
|
package/dist/index.mjs
CHANGED