syntropylog 0.12.7 → 0.12.9
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 +14 -0
- package/README.md +258 -174
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 384b630: **Docs:** README fully restructured for narrative coherence — sections now progress from infrastructure to data control, security, output, tracing, compliance, and production ops. Added item 15 (Hot reconfiguration per POD) to the feature table. Console transports section moved to Quick Start area as a setup clarification. "Key Concepts" and "Main Benefits" merged into a single "Core design" section to eliminate redundancy. Matrix in runtime moved to follow Logging Matrix (items 2→3). Sanitization and Serialization grouped after Masking. Universal Adapter and Fluent API repositioned after context features.
|
|
8
|
+
|
|
9
|
+
**Package:** `description` field updated to accurately reflect the framework's purpose.
|
|
10
|
+
|
|
11
|
+
## 0.12.8
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated documentation: clarified that the `audit` log level is immune to log level filtering and is always written.
|
|
16
|
+
|
|
3
17
|
## 0.12.7
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
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.
|
|
20
|
+
<a href="#"><img src="https://img.shields.io/badge/status-v0.12.9-brightgreen.svg" alt="Version 0.12.9"></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
|
|
|
@@ -42,23 +42,19 @@ SyntropyLog solves this by allowing you to:
|
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
-
###
|
|
45
|
+
### Core design
|
|
46
46
|
|
|
47
47
|
| Concept | What it does |
|
|
48
48
|
| :--- | :--- |
|
|
49
|
-
| **Native Addon (Rust)** |
|
|
50
|
-
| **Logging Matrix** | Declarative
|
|
51
|
-
| **MaskingEngine** | Real-time redaction of sensitive fields
|
|
52
|
-
| **Universal Adapter** |
|
|
49
|
+
| **Native Addon (Rust)** | Single-pass serialize + mask + sanitize at maximum speed. No CPU overhead on Node.js. |
|
|
50
|
+
| **Logging Matrix** | Declarative contract: which context fields appear per log level. Only declared fields are processed. |
|
|
51
|
+
| **MaskingEngine** | Real-time redaction of sensitive fields before any transport; built-in + custom rules. |
|
|
52
|
+
| **Universal Adapter** | One `executor` function → any backend (DB, Elasticsearch, S3, OTel). No vendor lock-in. |
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* **Extreme Performance:** Rust addon makes logging light on Node.js CPU.
|
|
59
|
-
* **Direct Compliance:** Facilitates audits (SOX, GDPR, PCI-DSS) with `audit` level and retention policies.
|
|
60
|
-
* **Active Security:** Sanitizes strings to prevent Log Injection attacks.
|
|
61
|
-
* **Traceability:** Manages `Correlation ID` and `Transaction ID` automatically.
|
|
54
|
+
* **Performance:** Rust addon keeps logging lightweight on Node.js CPU.
|
|
55
|
+
* **Compliance:** `audit` level + retention policies facilitate SOX, GDPR, PCI-DSS audits.
|
|
56
|
+
* **Security:** Masking + sanitization prevent data leakage and log injection.
|
|
57
|
+
* **Traceability:** Correlation ID and Transaction ID propagate automatically across all logs.
|
|
62
58
|
|
|
63
59
|
---
|
|
64
60
|
|
|
@@ -70,20 +66,21 @@ Everything below is part of the same stack (benchmarks use this full stack). Eac
|
|
|
70
66
|
|---|--------|---------------|
|
|
71
67
|
| 1 | **Native addon (Rust)** | Single-pass serialize + mask + sanitize; ANSI strip. Falls back to JS if unavailable. |
|
|
72
68
|
| 2 | **Logging Matrix** | Declarative control of which context fields appear per level (lean on `info`, full on `error`). |
|
|
73
|
-
| 3 | **
|
|
69
|
+
| 3 | **Matrix in runtime** | `reconfigureLoggingMatrix()` without restart; only field visibility, not security. |
|
|
74
70
|
| 4 | **MaskingEngine** | Redact sensitive fields before any transport; built-in + custom rules. |
|
|
75
|
-
| 5 | **
|
|
76
|
-
| 6 | **
|
|
71
|
+
| 5 | **SanitizationEngine** | Strip control characters; log injection resistant. |
|
|
72
|
+
| 6 | **Serialization pipeline** | Circular refs, depth limit, timeout; logging never blocks the event loop. |
|
|
77
73
|
| 7 | **Context / headers** | Correlation ID and transaction ID from config; single source of truth. |
|
|
78
|
-
| 8 | **
|
|
74
|
+
| 8 | **Universal Adapter** | Send logs to any backend (PostgreSQL, MongoDB, Elasticsearch, S3) via one `executor` function. |
|
|
79
75
|
| 9 | **Per-call transport control** | `.override()`, `.add()`, `.remove()` for one log call without new logger instances. |
|
|
80
|
-
| 10 | **
|
|
81
|
-
| 11 | **
|
|
82
|
-
| 12 | **
|
|
83
|
-
| 13 | **
|
|
84
|
-
| 14 | **
|
|
76
|
+
| 10 | **Fluent API** | `withRetention`, `withSource`, `withTransactionId` — bind once, carry on every log. |
|
|
77
|
+
| 11 | **Audit & retention** | `audit` level (always logged); `withRetention(anyJson)` for compliance routing. |
|
|
78
|
+
| 12 | **Lifecycle** | `init()` / `shutdown()`; graceful flush on SIGTERM/SIGINT. |
|
|
79
|
+
| 13 | **Observability hooks** | `onLogFailure`, `onTransportError`, `onSerializationFallback`, etc.; `isNativeAddonInUse()`. |
|
|
80
|
+
| 14 | **OpenTelemetry integration** | `UniversalAdapter` + `UniversalLogFormatter` → send logs to any OTel collector; no library changes needed. |
|
|
81
|
+
| 15 | **Hot reconfiguration (per POD)** | Change log level, add masking rules, or add a debug transport per POD at runtime via your own HTTP endpoint; no restart needed. |
|
|
85
82
|
|
|
86
|
-
**More detail and examples:** this README (English). [
|
|
83
|
+
**More detail and examples:** this README (English). See also [docs/features-and-examples.md](docs/features-and-examples.md). [También en español (ES)](doc-es/caracteristicas-y-ejemplos.md).
|
|
87
84
|
|
|
88
85
|
---
|
|
89
86
|
|
|
@@ -145,6 +142,33 @@ process.on('SIGINT', async () => {
|
|
|
145
142
|
|
|
146
143
|
---
|
|
147
144
|
|
|
145
|
+
## Console transports (default and pretty)
|
|
146
|
+
|
|
147
|
+
By default the library outputs **plain JSON** to the console. For colored, human-readable output in development, use a pretty transport:
|
|
148
|
+
|
|
149
|
+
| Transport | Style |
|
|
150
|
+
|-----------|--------|
|
|
151
|
+
| *(default)* | Plain JSON |
|
|
152
|
+
| `ClassicConsoleTransport` | Single-line, colored |
|
|
153
|
+
| `PrettyConsoleTransport` | Pretty-printed, colored |
|
|
154
|
+
| `CompactConsoleTransport` | Compact one-liner, colored |
|
|
155
|
+
| `ColorfulConsoleTransport` | Full-line colored |
|
|
156
|
+
|
|
157
|
+
Colors use built-in ANSI; no chalk. Disabled when stdout is not a TTY or when `NO_COLOR` is set.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { ClassicConsoleTransport } from 'syntropylog';
|
|
161
|
+
syntropyLog.init({
|
|
162
|
+
logger: {
|
|
163
|
+
level: 'info',
|
|
164
|
+
serviceName: 'my-app',
|
|
165
|
+
transports: [new ClassicConsoleTransport()],
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
148
172
|
## 1. Native addon (Rust)
|
|
149
173
|
|
|
150
174
|
**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).
|
|
@@ -157,13 +181,13 @@ if (syntropyLog.isNativeAddonInUse()) {
|
|
|
157
181
|
}
|
|
158
182
|
```
|
|
159
183
|
|
|
160
|
-
To build the native addon from source, see [
|
|
184
|
+
To build the native addon from source, see [docs/building-native-addon.md](docs/building-native-addon.md).
|
|
161
185
|
|
|
162
186
|
---
|
|
163
187
|
|
|
164
188
|
## 2. Logging Matrix
|
|
165
189
|
|
|
166
|
-
**What:** A JSON contract that defines exactly which context fields appear at each log level. If a field isn
|
|
190
|
+
**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.
|
|
167
191
|
|
|
168
192
|
**How:** Set `loggingMatrix` in `init()`:
|
|
169
193
|
|
|
@@ -189,89 +213,21 @@ await syntropyLog.init({
|
|
|
189
213
|
|
|
190
214
|
---
|
|
191
215
|
|
|
192
|
-
## 3.
|
|
193
|
-
|
|
194
|
-
**What:** Send each log to PostgreSQL, MongoDB, Elasticsearch, S3, etc. by implementing a single `executor`. No vendor lock-in. You define **once** how the log entry maps to your schema; the executor only receives that mapped object and persists it (ORM, raw client, HTTP). If `executor` throws, SyntropyLog logs the error and continues (Silent Observer).
|
|
195
|
-
|
|
196
|
-
**How:** Use `AdapterTransport` + `UniversalAdapter` + `UniversalLogFormatter`. Three steps:
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
### 1. Define the mapping (outside the executor)
|
|
201
|
-
|
|
202
|
-
Define how each log entry field maps to your persistence shape. One place, no repetition in code. Use `UniversalLogFormatter` with a `mapping` object: keys = your schema (e.g. DB columns), values = path in the log entry.
|
|
203
|
-
|
|
204
|
-
| Log entry path | Meaning |
|
|
205
|
-
|----------------|--------|
|
|
206
|
-
| `level` | Log level |
|
|
207
|
-
| `message` | Message string |
|
|
208
|
-
| `serviceName` | From init config |
|
|
209
|
-
| `correlationId` | From context |
|
|
210
|
-
| `timestamp` | ISO string |
|
|
211
|
-
| `meta` | Merged metadata (use as payload/JSON column) |
|
|
212
|
-
|
|
213
|
-
Example: map to a table with columns `level`, `message`, `serviceName`, `correlationId`, `payload`, `timestamp`.
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
import { UniversalLogFormatter } from 'syntropylog';
|
|
217
|
-
|
|
218
|
-
const logToDbMapping = {
|
|
219
|
-
level: 'level',
|
|
220
|
-
message: 'message',
|
|
221
|
-
serviceName: 'serviceName',
|
|
222
|
-
correlationId: 'correlationId',
|
|
223
|
-
payload: 'meta',
|
|
224
|
-
timestamp: 'timestamp',
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const formatter = new UniversalLogFormatter({ mapping: logToDbMapping });
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
The formatter turns every log entry into an object with exactly those keys. Change the mapping here when your schema changes; the executor stays the same.
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
### 2. The object the executor receives
|
|
235
|
-
|
|
236
|
-
When you attach this formatter to the transport, the `executor` receives **that mapped object** (not the raw log entry). So the executor only sees something like `{ level, message, serviceName, correlationId, payload, timestamp }`. Your only job in the executor is to **persist** that object.
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
### 3. Sending that object to your backend
|
|
216
|
+
## 3. Matrix in runtime
|
|
241
217
|
|
|
242
|
-
|
|
218
|
+
**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()`.
|
|
243
219
|
|
|
244
|
-
**
|
|
220
|
+
**How:**
|
|
245
221
|
|
|
246
222
|
```typescript
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
import { SystemLogModel } from './models/SystemLog';
|
|
252
|
-
|
|
253
|
-
const dbTransport = new AdapterTransport({
|
|
254
|
-
name: 'db',
|
|
255
|
-
formatter,
|
|
256
|
-
adapter: new UniversalAdapter({
|
|
257
|
-
executor: async (data) => {
|
|
258
|
-
const row = {
|
|
259
|
-
...data,
|
|
260
|
-
timestamp: new Date(data.timestamp as string),
|
|
261
|
-
};
|
|
262
|
-
// Same object, three destinations (e.g. Postgres + TypeORM DB + MongoDB)
|
|
263
|
-
await Promise.all([
|
|
264
|
-
prisma.systemLog.create({ data: row }),
|
|
265
|
-
getRepository(TypeOrmSystemLog).save(row),
|
|
266
|
-
SystemLogModel.create(row),
|
|
267
|
-
]);
|
|
268
|
-
},
|
|
269
|
-
}),
|
|
223
|
+
syntropyLog.reconfigureLoggingMatrix({
|
|
224
|
+
default: ['correlationId'],
|
|
225
|
+
info: ['correlationId', 'userId', 'operation'],
|
|
226
|
+
error: ['*'],
|
|
270
227
|
});
|
|
228
|
+
// Restore later with original matrix
|
|
271
229
|
```
|
|
272
230
|
|
|
273
|
-
Use one transport and one executor; add or remove destinations in that same block. Use this transport in your `init()` (e.g. in `logger.transports` or `logger.transportList` + `logger.env`).
|
|
274
|
-
|
|
275
231
|
---
|
|
276
232
|
|
|
277
233
|
## 4. MaskingEngine
|
|
@@ -344,7 +300,7 @@ masking: {
|
|
|
344
300
|
If you have a file in your project where you define **your own** sensitive words or aliases (e.g. `mySensitiveKeys.ts`), Sonar may report secrets (e.g. S2068) there. You can add an exception so that file does not block the deploy:
|
|
345
301
|
|
|
346
302
|
- **Exclude the file from analysis:** in your `sonar-project.properties`, add it to `sonar.exclusions` (e.g. `sonar.exclusions=**/mySensitiveKeys.ts`).
|
|
347
|
-
- **Or ignore only rule S2068 on that file:** use `sonar.issue.ignore.multicriteria` with `ruleKey=typescript:S2068` and `resourceKey` set to that file
|
|
303
|
+
- **Or ignore only rule S2068 on that file:** use `sonar.issue.ignore.multicriteria` with `ruleKey=typescript:S2068` and `resourceKey` set to that file's path.
|
|
348
304
|
|
|
349
305
|
Full steps and examples: [docs/SONAR_FILE_EXCEPTION.md](docs/SONAR_FILE_EXCEPTION.md).
|
|
350
306
|
|
|
@@ -359,7 +315,15 @@ If masking fails, the pipeline does not throw (Silent Observer). Custom rules: u
|
|
|
359
315
|
|
|
360
316
|
---
|
|
361
317
|
|
|
362
|
-
## 5.
|
|
318
|
+
## 5. SanitizationEngine
|
|
319
|
+
|
|
320
|
+
**What:** Strips control characters and ANSI from string values before any transport writes. Reduces log injection risk.
|
|
321
|
+
|
|
322
|
+
**How:** No configuration; it runs inside the pipeline. Together with the Logging Matrix (whitelist), it forms the safety boundary for log content.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 6. Serialization pipeline (resilience)
|
|
363
327
|
|
|
364
328
|
**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.
|
|
365
329
|
|
|
@@ -376,14 +340,6 @@ Logging never throws; failures are reported inside the log payload.
|
|
|
376
340
|
|
|
377
341
|
---
|
|
378
342
|
|
|
379
|
-
## 6. SanitizationEngine
|
|
380
|
-
|
|
381
|
-
**What:** Strips control characters and ANSI from string values before any transport writes. Reduces log injection risk.
|
|
382
|
-
|
|
383
|
-
**How:** No configuration; it runs inside the pipeline. Together with the Logging Matrix (whitelist), it forms the safety boundary for log content.
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
343
|
## 7. Context — correlation ID and transaction ID
|
|
388
344
|
|
|
389
345
|
**What:** One config defines header names; correlation and transaction IDs propagate to all logs and operations inside the same context (e.g. one request).
|
|
@@ -413,29 +369,88 @@ After that, every `logger.info(...)` inside the request carries the same `correl
|
|
|
413
369
|
|
|
414
370
|
---
|
|
415
371
|
|
|
416
|
-
## 8.
|
|
372
|
+
## 8. Universal Adapter — log to any backend
|
|
417
373
|
|
|
418
|
-
**What:**
|
|
374
|
+
**What:** Send each log to PostgreSQL, MongoDB, Elasticsearch, S3, etc. by implementing a single `executor`. No vendor lock-in. You define **once** how the log entry maps to your schema; the executor only receives that mapped object and persists it (ORM, raw client, HTTP). If `executor` throws, SyntropyLog logs the error and continues (Silent Observer).
|
|
419
375
|
|
|
420
|
-
**How:**
|
|
376
|
+
**How:** Use `AdapterTransport` + `UniversalAdapter` + `UniversalLogFormatter`. Three steps:
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### 1. Define the mapping (outside the executor)
|
|
381
|
+
|
|
382
|
+
Define how each log entry field maps to your persistence shape. One place, no repetition in code. Use `UniversalLogFormatter` with a `mapping` object: keys = your schema (e.g. DB columns), values = path in the log entry.
|
|
383
|
+
|
|
384
|
+
| Log entry path | Meaning |
|
|
385
|
+
|----------------|--------|
|
|
386
|
+
| `level` | Log level |
|
|
387
|
+
| `message` | Message string |
|
|
388
|
+
| `serviceName` | From init config |
|
|
389
|
+
| `correlationId` | From context |
|
|
390
|
+
| `timestamp` | ISO string |
|
|
391
|
+
| `meta` | Merged metadata (use as payload/JSON column) |
|
|
392
|
+
|
|
393
|
+
Example: map to a table with columns `level`, `message`, `serviceName`, `correlationId`, `payload`, `timestamp`.
|
|
421
394
|
|
|
422
395
|
```typescript
|
|
423
|
-
|
|
396
|
+
import { UniversalLogFormatter } from 'syntropylog';
|
|
424
397
|
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
398
|
+
const logToDbMapping = {
|
|
399
|
+
level: 'level',
|
|
400
|
+
message: 'message',
|
|
401
|
+
serviceName: 'serviceName',
|
|
402
|
+
correlationId: 'correlationId',
|
|
403
|
+
payload: 'meta',
|
|
404
|
+
timestamp: 'timestamp',
|
|
405
|
+
};
|
|
428
406
|
|
|
429
|
-
|
|
430
|
-
// Entry includes source and retention; your executor can route by retention.policy
|
|
407
|
+
const formatter = new UniversalLogFormatter({ mapping: logToDbMapping });
|
|
431
408
|
```
|
|
432
409
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
410
|
+
The formatter turns every log entry into an object with exactly those keys. Change the mapping here when your schema changes; the executor stays the same.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
### 2. The object the executor receives
|
|
415
|
+
|
|
416
|
+
When you attach this formatter to the transport, the `executor` receives **that mapped object** (not the raw log entry). So the executor only sees something like `{ level, message, serviceName, correlationId, payload, timestamp }`. Your only job in the executor is to **persist** that object.
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
### 3. Sending that object to your backend
|
|
421
|
+
|
|
422
|
+
One mapping produces one object shape. You can send **that same object** to as many backends as you want in a single executor: same `data`, different destinations (e.g. Prisma, TypeORM, Mongoose, Elasticsearch, S3). No field list — the shape comes from the mapping.
|
|
423
|
+
|
|
424
|
+
**Example: one mapped object → three destinations**
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
import { AdapterTransport, UniversalAdapter } from 'syntropylog';
|
|
428
|
+
import { prisma } from './prisma';
|
|
429
|
+
import { getRepository } from 'typeorm';
|
|
430
|
+
import { SystemLog as TypeOrmSystemLog } from './entities/SystemLog';
|
|
431
|
+
import { SystemLogModel } from './models/SystemLog';
|
|
432
|
+
|
|
433
|
+
const dbTransport = new AdapterTransport({
|
|
434
|
+
name: 'db',
|
|
435
|
+
formatter,
|
|
436
|
+
adapter: new UniversalAdapter({
|
|
437
|
+
executor: async (data) => {
|
|
438
|
+
const row = {
|
|
439
|
+
...data,
|
|
440
|
+
timestamp: new Date(data.timestamp as string),
|
|
441
|
+
};
|
|
442
|
+
// Same object, three destinations (e.g. Postgres + TypeORM DB + MongoDB)
|
|
443
|
+
await Promise.all([
|
|
444
|
+
prisma.systemLog.create({ data: row }),
|
|
445
|
+
getRepository(TypeOrmSystemLog).save(row),
|
|
446
|
+
SystemLogModel.create(row),
|
|
447
|
+
]);
|
|
448
|
+
},
|
|
449
|
+
}),
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Use one transport and one executor; add or remove destinations in that same block. Use this transport in your `init()` (e.g. in `logger.transports` or `logger.transportList` + `logger.env`).
|
|
439
454
|
|
|
440
455
|
---
|
|
441
456
|
|
|
@@ -477,7 +492,33 @@ See [examples/TRANSPORT_POOL_AND_ENV.md](examples/TRANSPORT_POOL_AND_ENV.md) and
|
|
|
477
492
|
|
|
478
493
|
---
|
|
479
494
|
|
|
480
|
-
## 10.
|
|
495
|
+
## 10. Fluent API — withRetention, withSource, withTransactionId
|
|
496
|
+
|
|
497
|
+
**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.
|
|
498
|
+
|
|
499
|
+
**How:**
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
const log = syntropyLog.getLogger();
|
|
503
|
+
|
|
504
|
+
const auditLogger = log
|
|
505
|
+
.withSource('PaymentService')
|
|
506
|
+
.withRetention({ policy: 'SOX_AUDIT_TRAIL', years: 5 });
|
|
507
|
+
|
|
508
|
+
auditLogger.audit({ userId: 123, action: 'payment' }, 'Payment processed');
|
|
509
|
+
// Entry includes source and retention; your executor can route by retention.policy
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
| Builder | Binds |
|
|
513
|
+
|---------|--------|
|
|
514
|
+
| `withSource('X')` | `source: 'X'` |
|
|
515
|
+
| `withTransactionId('id')` | `transactionId: 'id'` |
|
|
516
|
+
| `withRetention({ ... })` | `retention: { ... }` (any JSON) |
|
|
517
|
+
| `child({ k: v })` | arbitrary key-value |
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## 11. Audit and retention
|
|
481
522
|
|
|
482
523
|
**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.
|
|
483
524
|
|
|
@@ -493,9 +534,9 @@ Your `executor` can read `logEntry.retention?.policy` and persist to the right s
|
|
|
493
534
|
|
|
494
535
|
---
|
|
495
536
|
|
|
496
|
-
##
|
|
537
|
+
## 12. Lifecycle — init / shutdown
|
|
497
538
|
|
|
498
|
-
**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
|
|
539
|
+
**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.
|
|
499
540
|
|
|
500
541
|
**How:** Init is shown in Quick Start. Shutdown:
|
|
501
542
|
|
|
@@ -508,7 +549,7 @@ process.on('SIGTERM', async () => {
|
|
|
508
549
|
|
|
509
550
|
---
|
|
510
551
|
|
|
511
|
-
##
|
|
552
|
+
## 13. Observability hooks
|
|
512
553
|
|
|
513
554
|
**What:** Optional callbacks to observe failures without logging throwing: `onLogFailure`, `onTransportError`, `onSerializationFallback`, `onStepError`, `masking.onMaskingError`. Plus `isNativeAddonInUse()` at runtime.
|
|
514
555
|
|
|
@@ -534,59 +575,93 @@ await syntropyLog.init({
|
|
|
534
575
|
|
|
535
576
|
---
|
|
536
577
|
|
|
537
|
-
##
|
|
578
|
+
## 14. OpenTelemetry integration
|
|
538
579
|
|
|
539
|
-
**What:**
|
|
580
|
+
**What:** SyntropyLog requires no changes to integrate with OpenTelemetry. Define a formatter, write an executor that calls `otelLogger.emit()`, register it as a transport, and you're done.
|
|
540
581
|
|
|
541
582
|
**How:**
|
|
542
583
|
|
|
543
584
|
```typescript
|
|
544
|
-
syntropyLog
|
|
545
|
-
|
|
546
|
-
info: ['correlationId', 'userId', 'operation'],
|
|
547
|
-
error: ['*'],
|
|
548
|
-
});
|
|
549
|
-
// Restore later with original matrix
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
---
|
|
585
|
+
import { syntropyLog, AdapterTransport, UniversalAdapter, UniversalLogFormatter } from 'syntropylog';
|
|
586
|
+
import { logs, SeverityNumber } from '@opentelemetry/api-logs';
|
|
553
587
|
|
|
554
|
-
|
|
588
|
+
// 1. Formatter — maps SyntropyLog fields to OTel shape
|
|
589
|
+
const otelFormatter = new UniversalLogFormatter({
|
|
590
|
+
mapping: { body: 'message', severityText: 'level', timestamp: 'timestamp' },
|
|
591
|
+
includeAllIn: 'attributes',
|
|
592
|
+
});
|
|
555
593
|
|
|
556
|
-
|
|
594
|
+
// 2. Severity table
|
|
595
|
+
const SEVERITY_NUMBER: Record<string, number> = {
|
|
596
|
+
trace: 1, debug: 5, info: 9, audit: 9, warn: 13, error: 17, fatal: 21, silent: 0,
|
|
597
|
+
};
|
|
557
598
|
|
|
558
|
-
|
|
599
|
+
// 3. Executor — the bridge to OTel
|
|
600
|
+
function buildOtelExecutor(scopeName: string) {
|
|
601
|
+
return function executor(data: unknown): void {
|
|
602
|
+
const entry = data as { body: string; severityText: string; timestamp: string; attributes?: Record<string, unknown> };
|
|
603
|
+
const otelLogger = logs.getLogger(scopeName);
|
|
604
|
+
const ms = new Date(entry.timestamp).getTime();
|
|
605
|
+
const attrs = entry.attributes ?? {};
|
|
606
|
+
otelLogger.emit({
|
|
607
|
+
timestamp: [Math.floor(ms / 1000), (ms % 1000) * 1_000_000],
|
|
608
|
+
severityNumber: SEVERITY_NUMBER[entry.severityText] ?? SeverityNumber.UNSPECIFIED,
|
|
609
|
+
severityText: entry.severityText.toUpperCase(),
|
|
610
|
+
body: entry.body,
|
|
611
|
+
attributes: attrs,
|
|
612
|
+
traceId: typeof attrs.traceId === 'string' ? attrs.traceId : undefined,
|
|
613
|
+
spanId: typeof attrs.spanId === 'string' ? attrs.spanId : undefined,
|
|
614
|
+
traceFlags: typeof attrs.traceFlags === 'number' ? attrs.traceFlags : 1,
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
}
|
|
559
618
|
|
|
560
|
-
|
|
619
|
+
// 4. Transport
|
|
620
|
+
const otelTransport = new AdapterTransport({
|
|
621
|
+
name: 'otel',
|
|
622
|
+
adapter: new UniversalAdapter({ executor: buildOtelExecutor('my-service') }),
|
|
623
|
+
formatter: otelFormatter,
|
|
624
|
+
});
|
|
561
625
|
|
|
562
|
-
|
|
626
|
+
// 5. Init — wait for ready before logging
|
|
627
|
+
async function initializeSyntropyLog() {
|
|
628
|
+
return new Promise<void>((resolve, reject) => {
|
|
629
|
+
syntropyLog.on('ready', () => resolve());
|
|
630
|
+
syntropyLog.on('error', (err) => reject(err));
|
|
631
|
+
syntropyLog.init({
|
|
632
|
+
logger: {
|
|
633
|
+
serviceName: 'my-service',
|
|
634
|
+
level: 'info',
|
|
635
|
+
transportList: { otel: otelTransport },
|
|
636
|
+
},
|
|
637
|
+
loggingMatrix: {
|
|
638
|
+
info: ['correlationId', 'traceId', 'spanId'],
|
|
639
|
+
error: ['*'],
|
|
640
|
+
audit: ['*'],
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
}
|
|
563
645
|
|
|
564
|
-
|
|
646
|
+
// 6. Graceful shutdown
|
|
647
|
+
process.on('SIGTERM', async () => { await syntropyLog.shutdown(); process.exit(0); });
|
|
648
|
+
process.on('SIGINT', async () => { await syntropyLog.shutdown(); process.exit(0); });
|
|
565
649
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
650
|
+
async function main() {
|
|
651
|
+
await initializeSyntropyLog();
|
|
652
|
+
const log = syntropyLog.getLogger();
|
|
653
|
+
log.info({ traceId: 'abc123', spanId: 'def456' }, 'Payment processed');
|
|
654
|
+
}
|
|
655
|
+
main();
|
|
656
|
+
```
|
|
573
657
|
|
|
574
|
-
|
|
658
|
+
Per-call routing works the same as any other transport: `.override('otel')`, `.remove('otel')`, `.add('otel')`.
|
|
575
659
|
|
|
576
|
-
|
|
577
|
-
import { ClassicConsoleTransport } from 'syntropylog';
|
|
578
|
-
syntropyLog.init({
|
|
579
|
-
logger: {
|
|
580
|
-
level: 'info',
|
|
581
|
-
serviceName: 'my-app',
|
|
582
|
-
transports: [new ClassicConsoleTransport()],
|
|
583
|
-
},
|
|
584
|
-
});
|
|
585
|
-
```
|
|
660
|
+
For the full guide (formatter options, severity table, middleware injection, per-call routing): [docs/opentelemetry-integration.md](docs/opentelemetry-integration.md). [También en español](doc-es/integracion-opentelemetry.md).
|
|
586
661
|
|
|
587
662
|
---
|
|
588
663
|
|
|
589
|
-
## Reconfiguration in runtime (hot)
|
|
664
|
+
## 15. Reconfiguration in runtime (hot)
|
|
590
665
|
|
|
591
666
|
**The only things you can reconfigure without restart are:**
|
|
592
667
|
|
|
@@ -661,7 +736,7 @@ Secure this route (e.g. auth, internal only). When debugging in a POD is finishe
|
|
|
661
736
|
|
|
662
737
|
**Filesystem access:** The package only reads the files described below; it does not scan or read arbitrary paths.
|
|
663
738
|
|
|
664
|
-
- **Native addon loader** (`syntropylog-native`): Reads only (1) the presence of native `.node` binaries inside the package
|
|
739
|
+
- **Native addon loader** (`syntropylog-native`): Reads only (1) the presence of native `.node` binaries inside the package's own directory (`__dirname`) to choose the correct build for the current OS/arch, and (2) on Linux only, the system `ldd` binary (e.g. `/usr/bin/ldd`) to detect musl vs glibc. No user or application files are read.
|
|
665
740
|
Configuration is passed to `init()` only; the package does not load config from files.
|
|
666
741
|
|
|
667
742
|
| Dynamically configurable | Fixed at init |
|
|
@@ -674,21 +749,30 @@ Configuration is passed to `init()` only; the package does not load config from
|
|
|
674
749
|
|
|
675
750
|
**English (primary)**
|
|
676
751
|
|
|
677
|
-
- **This README** — Full picture, Quick Start, and a
|
|
752
|
+
- **This README** — Full picture, Quick Start, and a "How" section per feature (above).
|
|
678
753
|
- **[Examples repository](https://github.com/Syntropysoft/syntropylog-examples)** — Runnable examples 01–17: setup, context, transports, HTTP correlation, testing, benchmark.
|
|
679
754
|
- **[Transport pool and per-environment routing](examples/TRANSPORT_POOL_AND_ENV.md)** — `transportList`, `env`, override/add/remove; runnable [TransportPoolExample.ts](examples/TransportPoolExample.ts).
|
|
755
|
+
- **[Features and examples](docs/features-and-examples.md)** — Canonical stack list with explanations and code examples; aligned with the benchmark report.
|
|
756
|
+
- **[Benchmark report (throughput + memory)](docs/benchmark-report.md)** — Run `pnpm run bench` or `pnpm run bench:memory` from repo root.
|
|
757
|
+
- **[Benchmark memory run](docs/benchmark-memory-run.md)** — Detailed memory figures; compare native vs JS: `pnpm run bench` vs `SYNTROPYLOG_NATIVE_DISABLE=1 pnpm run bench`.
|
|
680
758
|
- **[Sensitive key aliases](docs/SENSITIVE_KEY_ALIASES.md)** — Recommended use of `maskEnum` (single object, declarative); full list of `MASK_KEY_*` and grouped arrays.
|
|
681
759
|
- **[Sonar: exception for a specific file](docs/SONAR_FILE_EXCEPTION.md)** — How to add a Sonar exception when you have your own file with sensitive words or aliases (so it does not block deploy).
|
|
760
|
+
- **[OpenTelemetry integration](docs/opentelemetry-integration.md)** — How to send logs to OTel using `UniversalAdapter` + `UniversalLogFormatter`; no library changes needed.
|
|
761
|
+
- **[Rust addon — build from source](docs/building-native-addon.md)** — Build instructions for macOS, Windows, Linux.
|
|
762
|
+
- **[Improvement plan & roadmap](docs/code-improvement-analysis-and-plan.md)** — Backlog and phased plan.
|
|
763
|
+
- **[Rust implementation plan](docs/rust-implementation-plan.md)** — Native addon checklist; links to [rust-pipeline-optimization.md](docs/rust-pipeline-optimization.md).
|
|
764
|
+
- **[Testing mocks](docs/testing-mocks.md)** — Public testing API: `SyntropyLogMock`, `createTestHelper`, etc.
|
|
682
765
|
- **[CONTRIBUTING.md](./CONTRIBUTING.md)** — How to contribute.
|
|
683
766
|
- **[SECURITY.md](./SECURITY.md)** — Security policy and environment variables.
|
|
684
767
|
|
|
685
768
|
**Spanish (ES)**
|
|
686
769
|
|
|
687
|
-
- **[
|
|
688
|
-
- **[
|
|
689
|
-
- **[Rust
|
|
690
|
-
- **[
|
|
691
|
-
- **[
|
|
770
|
+
- **[Características y ejemplos](doc-es/caracteristicas-y-ejemplos.md)** — Lista canónica del stack con explicaciones y ejemplos de código.
|
|
771
|
+
- **[Informe de benchmarks (throughput + memoria)](doc-es/benchmark-memory-run.md)** — `pnpm run bench` o `pnpm run bench:memory` desde la raíz del repo.
|
|
772
|
+
- **[Addon Rust — compilar desde fuente](doc-es/building-native-addon.es.md)**.
|
|
773
|
+
- **[Plan de mejoras y roadmap](doc-es/code-improvement-analysis-and-plan.md)** — Backlog y plan por fases.
|
|
774
|
+
- **[Plan de implementación Rust](doc-es/rust-implementation-plan.md)** — Checklist del addon nativo.
|
|
775
|
+
- **[Integración OpenTelemetry](doc-es/integracion-opentelemetry.md)** — Cómo enviar logs a OTel con `UniversalAdapter` + `UniversalLogFormatter`.
|
|
692
776
|
|
|
693
777
|
---
|
|
694
778
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "syntropylog",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20.0.0"
|
|
6
6
|
},
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "Structured observability framework for Node.js — declarative logging, masking, compliance, and tracing for high-demand environments.",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"instance-manager",
|
|
10
10
|
"instance-management",
|