syntropylog 0.11.1 → 0.11.3
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 +287 -472
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Refresh logo: README now points to syntropysoft.com/syntropylog-logo.png.
|
|
8
|
+
|
|
9
|
+
## 0.11.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
3
15
|
## 0.11.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SyntropyLog
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<img src="https://
|
|
4
|
+
<img src="https://syntropysoft.com/syntropylog-logo.png" alt="SyntropyLog Logo" width="170"/>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<h1 align="center">SyntropyLog</h1>
|
|
@@ -17,640 +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.3-brightgreen.svg" alt="Version 0.11.3"></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
|
-
|
|
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.
|
|
31
31
|
|
|
32
|
-
|
|
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
|
|
32
|
+
---
|
|
45
33
|
|
|
46
|
-
|
|
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).
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
- **SOX**: Immutable audit trail via `withRetention` bindings and dedicated transports.
|
|
57
|
+
---
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
## Quick Start
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
### Install
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
```bash
|
|
64
|
+
npm install syntropylog
|
|
65
|
+
```
|
|
56
66
|
|
|
57
|
-
|
|
67
|
+
Prebuilt native addon (Rust) for Linux, Windows, macOS installs automatically on Node ≥20. If unavailable, the JS pipeline is used transparently.
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
### Init and first log
|
|
60
70
|
|
|
61
|
-
|
|
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()`.
|
|
62
72
|
|
|
63
|
-
|
|
64
|
-
|
|
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.
|
|
73
|
+
```typescript
|
|
74
|
+
import { syntropyLog } from 'syntropylog';
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
}
|
|
73
85
|
|
|
74
|
-
|
|
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
|
+
```
|
|
75
93
|
|
|
76
|
-
|
|
94
|
+
### Graceful shutdown
|
|
77
95
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
});
|
|
81
105
|
```
|
|
82
106
|
|
|
83
|
-
|
|
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) |
|
|
84
114
|
|
|
85
|
-
|
|
115
|
+
---
|
|
86
116
|
|
|
87
|
-
|
|
117
|
+
## 1. Native addon (Rust)
|
|
88
118
|
|
|
89
|
-
|
|
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).
|
|
90
120
|
|
|
91
|
-
|
|
92
|
-
| :--- | :--- | :--- |
|
|
93
|
-
| *(default)* | Plain JSON | Production / log aggregators |
|
|
94
|
-
| `ClassicConsoleTransport` | Structured single-line, colored | Development |
|
|
95
|
-
| `PrettyConsoleTransport` | Human-readable pretty, colored | Development / debugging |
|
|
96
|
-
| `CompactConsoleTransport` | Compact one-liner, colored | Development |
|
|
97
|
-
| `ColorfulConsoleTransport` | Full-line colored | Development |
|
|
121
|
+
**How:** Nothing to configure. Check at runtime:
|
|
98
122
|
|
|
99
123
|
```typescript
|
|
100
|
-
|
|
101
|
-
|
|
124
|
+
if (syntropyLog.isNativeAddonInUse()) {
|
|
125
|
+
// Rust pipeline active
|
|
126
|
+
}
|
|
127
|
+
```
|
|
102
128
|
|
|
103
|
-
|
|
104
|
-
import { ClassicConsoleTransport } from 'syntropylog';
|
|
129
|
+
See [doc-es/building-native-addon.es.md](doc-es/building-native-addon.es.md) for building from source.
|
|
105
130
|
|
|
106
|
-
|
|
107
|
-
logger: {
|
|
108
|
-
level: 'info',
|
|
109
|
-
serviceName: 'my-app',
|
|
110
|
-
transports: [new ClassicConsoleTransport()],
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
```
|
|
131
|
+
---
|
|
114
132
|
|
|
115
|
-
|
|
133
|
+
## 2. Logging Matrix
|
|
116
134
|
|
|
117
|
-
|
|
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.
|
|
118
136
|
|
|
119
|
-
|
|
120
|
-
import { syntropyLog, ColorfulConsoleTransport, AdapterTransport, UniversalAdapter } from 'syntropylog';
|
|
121
|
-
|
|
122
|
-
const mockToConsole = (label: string) =>
|
|
123
|
-
new AdapterTransport({
|
|
124
|
-
name: label,
|
|
125
|
-
adapter: new UniversalAdapter({
|
|
126
|
-
executor: (data) => console.log(`[${label}]`, JSON.stringify(data)),
|
|
127
|
-
}),
|
|
128
|
-
});
|
|
137
|
+
**How:** Set `loggingMatrix` in `init()`:
|
|
129
138
|
|
|
139
|
+
```typescript
|
|
130
140
|
await syntropyLog.init({
|
|
131
|
-
logger: {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
},
|
|
139
|
-
env: {
|
|
140
|
-
development: ['consola'],
|
|
141
|
-
staging: ['consola', 'archivo', 'azure'],
|
|
142
|
-
production: ['consola', 'db', 'azure'],
|
|
143
|
-
},
|
|
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
|
|
144
148
|
},
|
|
145
|
-
redis: { instances: [] },
|
|
146
149
|
});
|
|
147
|
-
|
|
148
|
-
const log = syntropyLog.getLogger('app');
|
|
149
|
-
log.info('default according to env');
|
|
150
|
-
log.override('consola').info('only to console');
|
|
151
|
-
log.remove('db').add('archivo').info('default minus db, plus file');
|
|
152
150
|
```
|
|
153
151
|
|
|
154
|
-
|
|
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 |
|
|
155
158
|
|
|
156
159
|
---
|
|
157
160
|
|
|
158
|
-
|
|
159
|
-
import { syntropyLog } from 'syntropylog';
|
|
160
|
-
import { ClassicConsoleTransport } from 'syntropylog';
|
|
161
|
+
## 3. Universal Adapter — log to any backend
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
logger: {
|
|
164
|
-
level: 'info',
|
|
165
|
-
serviceName: 'ecommerce-app',
|
|
166
|
-
transports: [new ClassicConsoleTransport()],
|
|
167
|
-
},
|
|
168
|
-
context: {
|
|
169
|
-
correlationIdHeader: 'X-Correlation-ID', // defines the header name used everywhere
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// Always wait for the 'ready' event — see the Critical section below
|
|
174
|
-
async function initializeSyntropyLog() {
|
|
175
|
-
return new Promise<void>((resolve, reject) => {
|
|
176
|
-
syntropyLog.on('ready', () => resolve());
|
|
177
|
-
syntropyLog.on('error', (err) => reject(err));
|
|
178
|
-
syntropyLog.init(config);
|
|
179
|
-
});
|
|
180
|
-
}
|
|
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).
|
|
181
164
|
|
|
182
|
-
|
|
165
|
+
**How:** Use `AdapterTransport` + `UniversalAdapter`:
|
|
183
166
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
+
});
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
`logEntry` has: `level`, `message`, `serviceName`, `correlationId`, `timestamp`, `meta`. If `executor` throws, SyntropyLog logs the error and continues (Silent Observer).
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
---
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|------|--------------------|
|
|
195
|
-
| `onLogFailure?(error, entry)` | When a log call fails (serialization or transport error). |
|
|
196
|
-
| `onTransportError?(error, context)` | When a transport fails (flush, shutdown, or log write). `context` is `'flush'`, `'shutdown'`, or `'log'`. |
|
|
197
|
-
| `onSerializationFallback?(reason)` | When the native addon is used but fails for a call and the framework falls back to the JS pipeline. |
|
|
198
|
-
| `onStepError?(step, error)` | When a pipeline step fails (e.g. hygiene). |
|
|
199
|
-
| `masking.onMaskingError?(error)` | When masking fails (e.g. timeout); never receives raw payload. |
|
|
193
|
+
## 4. MaskingEngine
|
|
200
194
|
|
|
201
|
-
|
|
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.
|
|
202
196
|
|
|
203
|
-
|
|
197
|
+
**How:** Configure `masking` in `init()`:
|
|
204
198
|
|
|
205
|
-
### **3. Graceful Shutdown (Essential)**
|
|
206
199
|
```typescript
|
|
207
|
-
|
|
208
|
-
await syntropyLog.shutdown();
|
|
209
|
-
}
|
|
200
|
+
import { MaskingStrategy } from 'syntropylog';
|
|
210
201
|
|
|
211
|
-
|
|
212
|
-
|
|
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
|
+
});
|
|
213
217
|
```
|
|
214
218
|
|
|
215
|
-
|
|
219
|
+
| Strategy | Example output |
|
|
220
|
+
|----------|----------------|
|
|
221
|
+
| PASSWORD | `********` |
|
|
222
|
+
| EMAIL | `j***@example.com` |
|
|
223
|
+
| TOKEN | `eyJh...a1B9c` |
|
|
224
|
+
| CREDIT_CARD | `****-****-****-1234` |
|
|
216
225
|
|
|
217
|
-
|
|
226
|
+
If masking fails, the pipeline does not throw (Silent Observer). Custom rules: use ReDoS-safe regex; keys longer than 256 chars are skipped.
|
|
218
227
|
|
|
219
|
-
|
|
228
|
+
---
|
|
220
229
|
|
|
221
|
-
|
|
230
|
+
## 5. Serialization pipeline (resilience)
|
|
222
231
|
|
|
223
|
-
|
|
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.
|
|
224
233
|
|
|
225
|
-
|
|
234
|
+
**How:** Set timeout in logger config (e.g. 50–100ms):
|
|
226
235
|
|
|
227
236
|
```typescript
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
logger.info('This message is silently dropped.'); // ← init not complete yet
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### ✅ Correct pattern — event-based initialization
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
async function initializeSyntropyLog() {
|
|
238
|
-
return new Promise<void>((resolve, reject) => {
|
|
239
|
-
syntropyLog.on('ready', () => resolve()); // ← wait for this
|
|
240
|
-
syntropyLog.on('error', (err) => reject(err));
|
|
241
|
-
syntropyLog.init(config);
|
|
242
|
-
});
|
|
237
|
+
logger: {
|
|
238
|
+
serviceName: 'my-app',
|
|
239
|
+
serializerTimeoutMs: 100, // optional; e.g. 50–100ms for most apps
|
|
243
240
|
}
|
|
244
|
-
|
|
245
|
-
await initializeSyntropyLog(); // ← nothing runs before this resolves
|
|
246
|
-
const logger = syntropyLog.getLogger();
|
|
247
|
-
logger.info('System initialized and ready.');
|
|
248
241
|
```
|
|
249
242
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
| Framework | Bootstrap location |
|
|
253
|
-
| :--- | :--- |
|
|
254
|
-
| **Express / Fastify** | Call `await initializeSyntropyLog()` before `app.listen()` in `server.ts` / `main.ts` |
|
|
255
|
-
| **NestJS** | Inside `AppModule.onModuleInit()` or in `bootstrap()` before `app.listen()` |
|
|
256
|
-
| **Lambda / Serverless** | Module-level block (outside the handler), using a lazy singleton pattern |
|
|
243
|
+
Logging never throws; failures are reported inside the log payload.
|
|
257
244
|
|
|
258
245
|
---
|
|
259
246
|
|
|
260
|
-
##
|
|
247
|
+
## 6. SanitizationEngine
|
|
248
|
+
|
|
249
|
+
**What:** Strips control characters and ANSI from string values before any transport writes. Reduces log injection risk.
|
|
261
250
|
|
|
262
|
-
|
|
251
|
+
**How:** No configuration; it runs inside the pipeline. Together with the Logging Matrix (whitelist), it forms the safety boundary for log content.
|
|
263
252
|
|
|
264
|
-
|
|
253
|
+
---
|
|
265
254
|
|
|
266
|
-
|
|
255
|
+
## 7. Context — correlation ID and transaction ID
|
|
267
256
|
|
|
268
|
-
|
|
269
|
-
// Defined once in init()
|
|
270
|
-
context: {
|
|
271
|
-
correlationIdHeader: 'X-Correlation-ID',
|
|
272
|
-
}
|
|
273
|
-
```
|
|
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).
|
|
274
258
|
|
|
275
|
-
|
|
259
|
+
**How:** Set `context` in `init()` and use a small middleware:
|
|
276
260
|
|
|
277
261
|
```typescript
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
logger.info('User logged in'); // correlationId attached automatically
|
|
262
|
+
await syntropyLog.init({
|
|
263
|
+
context: {
|
|
264
|
+
correlationIdHeader: 'X-Correlation-ID',
|
|
265
|
+
transactionIdHeader: 'X-Transaction-ID',
|
|
266
|
+
},
|
|
284
267
|
});
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### 🔮 The Magic Middleware (2 Lines of Code)
|
|
288
|
-
|
|
289
|
-
Add this **once** to your Express/Fastify app and never think about Correlation IDs again:
|
|
290
268
|
|
|
291
|
-
|
|
269
|
+
// Express/Fastify middleware (once per app)
|
|
270
|
+
const { contextManager } = syntropyLog;
|
|
292
271
|
app.use(async (req, res, next) => {
|
|
293
272
|
await contextManager.run(async () => {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
contextManager.set(contextManager.getCorrelationIdHeaderName(), correlationId); // Sets in context
|
|
297
|
-
|
|
273
|
+
const correlationId = contextManager.getCorrelationId();
|
|
274
|
+
contextManager.set(contextManager.getCorrelationIdHeaderName(), correlationId);
|
|
298
275
|
next();
|
|
299
276
|
});
|
|
300
277
|
});
|
|
301
278
|
```
|
|
302
279
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
- **Intelligent Detection**: `getCorrelationId()` uses the existing ID from the incoming request or generates a new one
|
|
306
|
-
- **Automatic Configuration**: `getCorrelationIdHeaderName()` reads your `context.correlationIdHeader` config — change it once, updated everywhere
|
|
307
|
-
- **Automatic Propagation**: Once set, it propagates to all logs and operations
|
|
280
|
+
After that, every `logger.info(...)` inside the request carries the same `correlationId` without passing it manually.
|
|
308
281
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
In production, context is created automatically by SyntropyLog's built-in adapters (no manual `run()` needed):
|
|
312
|
-
|
|
313
|
-
- HTTP middleware (Express, Fastify, Koa)
|
|
314
|
-
- Message queue handlers (Kafka, RabbitMQ)
|
|
315
|
-
- Background job processors / API gateways
|
|
282
|
+
---
|
|
316
283
|
|
|
317
|
-
|
|
318
|
-
| :--- | :--- |
|
|
319
|
-
| **Examples & quick tests** | Wrap logging in `contextManager.run()` manually |
|
|
320
|
-
| **Production apps** | Use SyntropyLog's HTTP/broker adapters — context is automatic |
|
|
284
|
+
## 8. Fluent API — withRetention, withSource, withTransactionId
|
|
321
285
|
|
|
322
|
-
|
|
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.
|
|
323
287
|
|
|
324
|
-
|
|
288
|
+
**How:**
|
|
325
289
|
|
|
326
|
-
|
|
290
|
+
```typescript
|
|
291
|
+
const log = syntropyLog.getLogger();
|
|
327
292
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
{ "userId": 123, "operation": "user-login" }
|
|
332
|
-
```
|
|
293
|
+
const auditLogger = log
|
|
294
|
+
.withSource('PaymentService')
|
|
295
|
+
.withRetention({ policy: 'SOX_AUDIT_TRAIL', years: 5 });
|
|
333
296
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
12:56:00 [ERROR] (ecommerce-app): User request processed { status: 'completed', duration: '150ms' }
|
|
337
|
-
{
|
|
338
|
-
"userId": 123, "email": "user@example.com", "password": "***MASKED***",
|
|
339
|
-
"firstName": "John", "ipAddress": "127.0.0.1",
|
|
340
|
-
"sessionId": "sess-789", "requestId": "req-456"
|
|
341
|
-
}
|
|
297
|
+
auditLogger.info({ userId: 123, action: 'payment' }, 'Payment processed');
|
|
298
|
+
// Entry includes source and retention; your executor can route by retention.policy
|
|
342
299
|
```
|
|
343
300
|
|
|
344
|
-
|
|
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 |
|
|
345
307
|
|
|
346
308
|
---
|
|
347
309
|
|
|
348
|
-
##
|
|
349
|
-
|
|
350
|
-
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.**
|
|
310
|
+
## 9. Per-call transport control
|
|
351
311
|
|
|
352
|
-
|
|
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.
|
|
353
313
|
|
|
354
|
-
|
|
314
|
+
**How:** Define a transport pool with `transportList` and `env`, then use the fluent methods on the next call only:
|
|
355
315
|
|
|
356
316
|
```typescript
|
|
357
317
|
await syntropyLog.init({
|
|
358
|
-
logger: {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
// error/fatal: everything — full context to debug the failure
|
|
371
|
-
error: ['correlationId', 'userId', 'operation', 'errorCode', 'tenantId', 'orderId'],
|
|
372
|
-
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
|
+
},
|
|
373
329
|
},
|
|
374
330
|
});
|
|
375
|
-
```
|
|
376
331
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
| `transactionId` | `x-trace-id`, `transactionId` |
|
|
383
|
-
| `userId` | `userId` |
|
|
384
|
-
| `tenantId` | `tenantId` |
|
|
385
|
-
| `operation` | `operation` |
|
|
386
|
-
| `errorCode` | `errorCode` |
|
|
387
|
-
| `orderId` | `orderId` |
|
|
388
|
-
| `paymentId` | `paymentId` |
|
|
389
|
-
| `eventType` | `eventType` |
|
|
390
|
-
| `*` | 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
|
+
```
|
|
391
337
|
|
|
392
|
-
|
|
338
|
+
See [examples/TRANSPORT_POOL_AND_ENV.md](examples/TRANSPORT_POOL_AND_ENV.md) and `examples/TransportPoolExample.ts`.
|
|
393
339
|
|
|
394
|
-
|
|
340
|
+
---
|
|
395
341
|
|
|
396
|
-
|
|
397
|
-
1. **Logging Matrix** — whitelist filter: only declared fields pass through
|
|
398
|
-
2. **SanitizationEngine** — strips control characters from string values before output
|
|
342
|
+
## 10. Audit and retention
|
|
399
343
|
|
|
400
|
-
|
|
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.
|
|
401
345
|
|
|
402
|
-
|
|
346
|
+
**How:**
|
|
403
347
|
|
|
404
348
|
```typescript
|
|
405
|
-
const {
|
|
406
|
-
|
|
407
|
-
//
|
|
408
|
-
contextManager.reconfigureLoggingMatrix({
|
|
409
|
-
default: ['correlationId'],
|
|
410
|
-
info: ['correlationId', 'userId', 'operation'],
|
|
411
|
-
error: ['*'], // full context on errors
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
// Later, restore original config
|
|
415
|
-
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
|
|
416
352
|
```
|
|
417
353
|
|
|
418
|
-
|
|
354
|
+
Your `executor` can read `logEntry.retention?.policy` and persist to the right store.
|
|
419
355
|
|
|
420
356
|
---
|
|
421
357
|
|
|
422
|
-
##
|
|
423
|
-
|
|
424
|
-
SyntropyLog ships with a **MaskingEngine** that automatically redacts sensitive fields in every log — before they ever reach a transport or database.
|
|
425
|
-
|
|
426
|
-
### Built-in strategies (enabled by default)
|
|
358
|
+
## 11. Lifecycle — init / shutdown
|
|
427
359
|
|
|
428
|
-
|
|
429
|
-
| :--- | :--- | :--- |
|
|
430
|
-
| `PASSWORD` | `password`, `pass`, `pwd`, `secret` | `********` |
|
|
431
|
-
| `EMAIL` | `email` | `j***@example.com` |
|
|
432
|
-
| `TOKEN` | `token`, `api_key`, `auth_token`, `jwt`, `bearer` | `eyJh...a1B9c` |
|
|
433
|
-
| `CREDIT_CARD` | `credit_card`, `card_number`, `payment_number` | `****-****-****-1234` |
|
|
434
|
-
| `SSN` | `ssn`, `social_security`, `security_number` | `***-**-6789` |
|
|
435
|
-
| `PHONE` | `phone`, `phone_number`, `mobile_number` | `***-***-4567` |
|
|
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.
|
|
436
361
|
|
|
437
|
-
|
|
362
|
+
**How:** Init is shown in Quick Start. Shutdown:
|
|
438
363
|
|
|
439
364
|
```typescript
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
logger: { ... },
|
|
444
|
-
context: { ... },
|
|
445
|
-
masking: {
|
|
446
|
-
// Default rules are ON — set to false to start from scratch
|
|
447
|
-
enableDefaultRules: true,
|
|
448
|
-
|
|
449
|
-
// Global mask character
|
|
450
|
-
maskChar: '*',
|
|
451
|
-
|
|
452
|
-
// Preserve original field length by default
|
|
453
|
-
preserveLength: true,
|
|
454
|
-
|
|
455
|
-
// Add your own rules on top of the defaults
|
|
456
|
-
rules: [
|
|
457
|
-
{
|
|
458
|
-
// Mask any field whose name contains 'cuit' or 'cuil'
|
|
459
|
-
pattern: /cuit|cuil/i,
|
|
460
|
-
strategy: MaskingStrategy.CUSTOM,
|
|
461
|
-
customMask: (value) => value.replace(/\d(?=\d{4})/g, '*'), // keep last 4 digits
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
// Mask internal API keys
|
|
465
|
-
pattern: /internal_key|service_secret/i,
|
|
466
|
-
strategy: MaskingStrategy.TOKEN,
|
|
467
|
-
},
|
|
468
|
-
],
|
|
469
|
-
},
|
|
365
|
+
process.on('SIGTERM', async () => {
|
|
366
|
+
await syntropyLog.shutdown();
|
|
367
|
+
process.exit(0);
|
|
470
368
|
});
|
|
471
369
|
```
|
|
472
370
|
|
|
473
|
-
> **Silent Observer guarantee**: if the masking engine fails for any reason, it returns the original object and the application keeps running — it never throws.
|
|
474
|
-
|
|
475
|
-
**Performance**: Built-in rules use synchronous regex matching (safe, known patterns). Custom rules use the same synchronous engine for maximum speed.
|
|
476
|
-
|
|
477
|
-
> ⚠️ **Security Warning (Custom Rules)**
|
|
478
|
-
> 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.
|
|
479
|
-
|
|
480
371
|
---
|
|
481
372
|
|
|
482
|
-
##
|
|
373
|
+
## 12. Observability hooks
|
|
483
374
|
|
|
484
|
-
|
|
375
|
+
**What:** Optional callbacks to observe failures without logging throwing: `onLogFailure`, `onTransportError`, `onSerializationFallback`, `onStepError`, `masking.onMaskingError`. Plus `isNativeAddonInUse()` at runtime.
|
|
485
376
|
|
|
486
|
-
|
|
487
|
-
import { UniversalAdapter } from 'syntropylog';
|
|
488
|
-
import { prisma } from './db'; // your Prisma client, Mongoose model, pg pool, etc.
|
|
489
|
-
|
|
490
|
-
const dbTransport = new UniversalAdapter({
|
|
491
|
-
executor: async (logEntry) => {
|
|
492
|
-
// logEntry is the fully-formed, masked log object
|
|
493
|
-
await prisma.systemLog.create({
|
|
494
|
-
data: {
|
|
495
|
-
level: logEntry.level,
|
|
496
|
-
message: logEntry.message,
|
|
497
|
-
service: logEntry.serviceName,
|
|
498
|
-
correlationId: logEntry.correlationId,
|
|
499
|
-
payload: logEntry.meta, // JSON column
|
|
500
|
-
timestamp: new Date(logEntry.timestamp),
|
|
501
|
-
},
|
|
502
|
-
});
|
|
503
|
-
},
|
|
504
|
-
});
|
|
377
|
+
**How:** Pass them in `init()`:
|
|
505
378
|
|
|
379
|
+
```typescript
|
|
506
380
|
await syntropyLog.init({
|
|
507
|
-
logger: {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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') },
|
|
512
386
|
});
|
|
513
387
|
```
|
|
514
388
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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 |
|
|
518
396
|
|
|
519
|
-
|
|
520
|
-
| :--- | :--- | :--- |
|
|
521
|
-
| `level` | `string` | Log level: `info`, `error`, etc. |
|
|
522
|
-
| `message` | `string` | The log message |
|
|
523
|
-
| `serviceName` | `string` | From your config |
|
|
524
|
-
| `correlationId` | `string` | From the active context |
|
|
525
|
-
| `timestamp` | `number` | Unix timestamp |
|
|
526
|
-
| `meta` | `object` | Any extra fields passed to the log call — already masked |
|
|
527
|
-
|
|
528
|
-
> **Silent Observer**: if your `executor` throws, SyntropyLog logs the error to console and continues — your app never crashes because of a log transport failure.
|
|
529
|
-
|
|
530
|
-
### 🔗 Fluent Logger API — Specialized Loggers
|
|
531
|
-
|
|
532
|
-
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.
|
|
533
|
-
|
|
534
|
-
| Builder | What it binds | Typical use |
|
|
535
|
-
| :--- | :--- | :--- |
|
|
536
|
-
| `withSource('ModuleName')` | `source: 'ModuleName'` | Tag logs by module or component |
|
|
537
|
-
| `withTransactionId('txn-123')` | `transactionId: 'txn-123'` | Track a business transaction ID |
|
|
538
|
-
| `withRetention({ ...anyJson })` | `retention: { ...anyJson }` | Attach org-defined metadata to route logs |
|
|
539
|
-
| `child({ key: value })` | Any key-value pairs | General-purpose bindings |
|
|
397
|
+
---
|
|
540
398
|
|
|
541
|
-
|
|
399
|
+
## 13. Matrix in runtime
|
|
542
400
|
|
|
543
|
-
|
|
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()`.
|
|
544
402
|
|
|
545
|
-
|
|
403
|
+
**How:**
|
|
546
404
|
|
|
547
405
|
```typescript
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
policy: 'GDPR_ARTICLE_17',
|
|
553
|
-
years: 7,
|
|
554
|
-
immutable: true,
|
|
555
|
-
region: 'eu-west-1',
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
const auditLogger = logger.withRetention({
|
|
559
|
-
policy: 'SOX_AUDIT_TRAIL',
|
|
560
|
-
years: 5,
|
|
406
|
+
syntropyLog.reconfigureLoggingMatrix({
|
|
407
|
+
default: ['correlationId'],
|
|
408
|
+
info: ['correlationId', 'userId', 'operation'],
|
|
409
|
+
error: ['*'],
|
|
561
410
|
});
|
|
411
|
+
// Restore later with original matrix
|
|
412
|
+
```
|
|
562
413
|
|
|
563
|
-
|
|
564
|
-
policy: 'EPHEMERAL',
|
|
565
|
-
days: 7,
|
|
566
|
-
tier: 'hot',
|
|
567
|
-
});
|
|
414
|
+
---
|
|
568
415
|
|
|
569
|
-
|
|
570
|
-
const paymentAuditLogger = logger
|
|
571
|
-
.withSource('PaymentService')
|
|
572
|
-
.withRetention({ policy: 'PCI_DSS', years: 5 });
|
|
573
|
-
```
|
|
416
|
+
## 14. Tree-shaking
|
|
574
417
|
|
|
575
|
-
|
|
418
|
+
**What:** Package is published with `sideEffects: false` and ESM so bundlers include only what you import.
|
|
576
419
|
|
|
577
|
-
|
|
578
|
-
// In PaymentService.ts
|
|
579
|
-
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.
|
|
580
421
|
|
|
581
|
-
|
|
582
|
-
auditLogger.warn({ userId: 456, action: 'login-failed' }, 'Security event');
|
|
583
|
-
```
|
|
422
|
+
---
|
|
584
423
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
```json
|
|
588
|
-
{
|
|
589
|
-
"level": "info",
|
|
590
|
-
"message": "GDPR event",
|
|
591
|
-
"correlationId": "uuid-...",
|
|
592
|
-
"source": "PaymentService",
|
|
593
|
-
"retention": { "policy": "GDPR_ARTICLE_17", "years": 7, "immutable": true },
|
|
594
|
-
"userId": 123,
|
|
595
|
-
"action": "data-export"
|
|
596
|
-
}
|
|
597
|
-
```
|
|
424
|
+
## Console transports (default and pretty)
|
|
598
425
|
|
|
599
|
-
|
|
426
|
+
By default the library outputs **plain JSON** to the console. For colored, human-readable output in development, use a pretty transport:
|
|
600
427
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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 |
|
|
605
435
|
|
|
606
|
-
|
|
607
|
-
policy === 'GDPR_ARTICLE_17' || policy === 'SOX_AUDIT_TRAIL' ? 'audit_logs'
|
|
608
|
-
: policy === 'PCI_DSS' ? 'payment_audit_logs'
|
|
609
|
-
: policy === 'EPHEMERAL' ? 'debug_logs'
|
|
610
|
-
: 'system_logs';
|
|
436
|
+
Colors use built-in ANSI; no chalk. Disabled when stdout is not a TTY or when `NO_COLOR` is set.
|
|
611
437
|
|
|
612
|
-
|
|
438
|
+
```typescript
|
|
439
|
+
import { ClassicConsoleTransport } from 'syntropylog';
|
|
440
|
+
syntropyLog.init({
|
|
441
|
+
logger: {
|
|
442
|
+
level: 'info',
|
|
443
|
+
serviceName: 'my-app',
|
|
444
|
+
transports: [new ClassicConsoleTransport()],
|
|
613
445
|
},
|
|
614
446
|
});
|
|
615
447
|
```
|
|
616
448
|
|
|
617
|
-
> **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.
|
|
618
|
-
|
|
619
|
-
---
|
|
620
|
-
|
|
621
|
-
## ✨ Key Capabilities
|
|
622
|
-
|
|
623
|
-
| Feature | Description |
|
|
624
|
-
| :--- | :--- |
|
|
625
|
-
| **Instance Management** | Register shared resources once, use them everywhere with confidence. |
|
|
626
|
-
| **Correlation Tracking** | Trace requests across multiple services and DBs automatically. |
|
|
627
|
-
| **Silent Observer** | If logging fails, your application keeps running perfectly. |
|
|
628
|
-
| **Universal Persistence** | Map logs to ANY database (SQL/NoSQL) with pure JSON mapping. |
|
|
629
|
-
|
|
630
|
-
---
|
|
631
|
-
|
|
632
|
-
---
|
|
633
|
-
|
|
634
449
|
---
|
|
635
450
|
|
|
636
|
-
##
|
|
451
|
+
## Security & Compliance
|
|
637
452
|
|
|
638
|
-
|
|
|
639
|
-
|
|
640
|
-
|
|
|
641
|
-
| 🔒 **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 |
|
|
642
456
|
|
|
643
457
|
---
|
|
644
458
|
|
|
645
|
-
##
|
|
459
|
+
## Documentation
|
|
646
460
|
|
|
647
|
-
- **[
|
|
648
|
-
- **[
|
|
649
|
-
- **
|
|
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).
|
|
650
466
|
|
|
651
467
|
---
|
|
652
468
|
|
|
653
|
-
##
|
|
469
|
+
## Contributing & License
|
|
654
470
|
|
|
655
|
-
|
|
656
|
-
Project licensed under **Apache-2.0**.
|
|
471
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). License: **Apache-2.0**.
|