logixia 1.10.3 → 1.11.1
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/README.md +180 -33
- package/dist/.tsbuildinfo +1 -1
- package/dist/{index-Co47qPnq.d.mts → index-CSFeEGLb.d.ts} +32 -2
- package/dist/index-CSFeEGLb.d.ts.map +1 -0
- package/dist/{index-F-A7hg1u.d.ts → index-Cw-sN_0_.d.mts} +32 -2
- package/dist/index-Cw-sN_0_.d.mts.map +1 -0
- package/dist/index.d.mts +251 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +251 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +500 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +488 -7
- package/dist/index.mjs.map +1 -1
- package/dist/{logitron-logger.module-Dlf5GwJ9.js → logitron-logger.module-CLfa-Hbx.js} +120 -16
- package/dist/logitron-logger.module-CLfa-Hbx.js.map +1 -0
- package/dist/{logitron-logger.module-BBC9nO5q.mjs → logitron-logger.module-CgxjNLRi.mjs} +120 -16
- package/dist/logitron-logger.module-CgxjNLRi.mjs.map +1 -0
- package/dist/{logitron-logger.module-BLT1y5Iq.d.ts → logitron-logger.module-Je6hozHh.d.mts} +33 -4
- package/dist/logitron-logger.module-Je6hozHh.d.mts.map +1 -0
- package/dist/{logitron-logger.module-B8NklSC4.d.mts → logitron-logger.module-VhxcmlZZ.d.ts} +33 -4
- package/dist/logitron-logger.module-VhxcmlZZ.d.ts.map +1 -0
- package/dist/middleware.d.mts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/nest.d.mts +2 -2
- package/dist/nest.d.ts +2 -2
- package/dist/nest.js +2 -2
- package/dist/nest.mjs +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{transport.manager-Cij_sA-b.mjs → transport.manager-DETpXuaY.mjs} +56 -13
- package/dist/transport.manager-DETpXuaY.mjs.map +1 -0
- package/dist/{transport.manager-B9LF9uDd.js → transport.manager-ciG4r5uz.js} +56 -13
- package/dist/transport.manager-ciG4r5uz.js.map +1 -0
- package/dist/transports.d.mts +15 -3
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +15 -3
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +1 -1
- package/dist/transports.mjs +1 -1
- package/package.json +44 -4
- package/dist/index-Co47qPnq.d.mts.map +0 -1
- package/dist/index-F-A7hg1u.d.ts.map +0 -1
- package/dist/logitron-logger.module-B8NklSC4.d.mts.map +0 -1
- package/dist/logitron-logger.module-BBC9nO5q.mjs.map +0 -1
- package/dist/logitron-logger.module-BLT1y5Iq.d.ts.map +0 -1
- package/dist/logitron-logger.module-Dlf5GwJ9.js.map +0 -1
- package/dist/transport.manager-B9LF9uDd.js.map +0 -1
- package/dist/transport.manager-Cij_sA-b.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# logixia
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<strong>The async-first
|
|
5
|
-
|
|
4
|
+
<strong>The async-first TypeScript logger that ships complete.</strong><br/>
|
|
5
|
+
Wide events · OpenTelemetry (OTLP) · Runtime log levels · Redaction · Adaptive sampling<br/>
|
|
6
|
+
NestJS · Express · Fastify · Database · Cloud · Tracing · Prometheus · Browser
|
|
6
7
|
</p>
|
|
7
8
|
|
|
8
9
|
<p align="center">
|
|
@@ -108,6 +109,11 @@ await logger.info('Server started', { port: 3000 });
|
|
|
108
109
|
- [Transport filter predicate](#transport-filter-predicate)
|
|
109
110
|
- [Log search](#log-search)
|
|
110
111
|
- [OpenTelemetry](#opentelemetry)
|
|
112
|
+
- [OTLP logs export](#otlp-logs-export-opentelemetry-native)
|
|
113
|
+
- [Wide events / canonical log lines](#wide-events--canonical-log-lines)
|
|
114
|
+
- [Dynamic runtime reconfiguration](#dynamic-runtime-reconfiguration)
|
|
115
|
+
- [Adaptive sampling](#adaptive-sampling)
|
|
116
|
+
- [Robust serialization](#robust-serialization)
|
|
111
117
|
- [Graceful shutdown](#graceful-shutdown)
|
|
112
118
|
- [Plugin / extension API](#plugin--extension-api)
|
|
113
119
|
- [Writing a plugin](#writing-a-plugin)
|
|
@@ -158,45 +164,70 @@ logixia takes a different approach: **everything ships built-in, and nothing blo
|
|
|
158
164
|
|
|
159
165
|
## Feature comparison
|
|
160
166
|
|
|
161
|
-
| Feature
|
|
162
|
-
|
|
|
163
|
-
| TypeScript-first
|
|
164
|
-
| Async / non-blocking writes
|
|
165
|
-
| NestJS module (built-in)
|
|
166
|
-
| Database transports (built-in)
|
|
167
|
-
| Cloud transports (CW, GCP, Azure)
|
|
168
|
-
| File rotation (built-in)
|
|
169
|
-
| Multi-transport concurrent
|
|
170
|
-
| Log search
|
|
171
|
-
| Field redaction (built-in)
|
|
172
|
-
| Request tracing (AsyncLocalStorage)
|
|
173
|
-
| Kafka + WebSocket trace interceptors
|
|
174
|
-
| Correlation ID propagation
|
|
175
|
-
| Browser / Edge / Bun / Deno support
|
|
176
|
-
| OpenTelemetry / W3C headers
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
167
|
+
| Feature | **logixia** | pino | winston | bunyan |
|
|
168
|
+
| ------------------------------------- | :---------: | :---------: | :-----------------------: | :-----: |
|
|
169
|
+
| TypeScript-first | yes | partial | partial | partial |
|
|
170
|
+
| Async / non-blocking writes | yes | no | no | no |
|
|
171
|
+
| NestJS module (built-in) | yes | no | no | no |
|
|
172
|
+
| Database transports (built-in) | yes | no | no | no |
|
|
173
|
+
| Cloud transports (CW, GCP, Azure) | yes | no | no | no |
|
|
174
|
+
| File rotation (built-in) | yes | pino-roll | winston-daily-rotate-file | no |
|
|
175
|
+
| Multi-transport concurrent | yes | no | yes | no |
|
|
176
|
+
| Log search | yes | no | no | no |
|
|
177
|
+
| Field + message redaction (built-in) | yes | pino-redact | no | no |
|
|
178
|
+
| Request tracing (AsyncLocalStorage) | yes | no | no | no |
|
|
179
|
+
| Kafka + WebSocket trace interceptors | yes | no | no | no |
|
|
180
|
+
| Correlation ID propagation | yes | no | no | no |
|
|
181
|
+
| Browser / Edge / Bun / Deno support | yes | partial | no | no |
|
|
182
|
+
| OpenTelemetry / W3C headers | yes | no | no | no |
|
|
183
|
+
| **OTLP logs export (OTel-native)** | **yes** | transport | no | no |
|
|
184
|
+
| **Wide events / canonical log lines** | **yes** | no | no | no |
|
|
185
|
+
| **Runtime log-level reconfig** | **yes** | external | no | no |
|
|
186
|
+
| **Adaptive (anomaly) sampling** | **yes** | no | no | no |
|
|
187
|
+
| Graceful shutdown / flush (no loss) | yes | partial | no | no |
|
|
188
|
+
| Custom log levels | yes | yes | yes | yes |
|
|
189
|
+
| Adaptive log level (NODE_ENV) | yes | no | no | no |
|
|
190
|
+
| Plugin / extension API | yes | no | no | no |
|
|
191
|
+
| Prometheus metrics extraction | yes | no | no | no |
|
|
192
|
+
| Visual TUI log explorer | yes | no | no | no |
|
|
193
|
+
| Actively maintained | yes | yes | yes | no |
|
|
184
194
|
|
|
185
195
|
---
|
|
186
196
|
|
|
187
197
|
## Performance
|
|
188
198
|
|
|
189
|
-
logixia
|
|
199
|
+
logixia is async-first and built for the hot path: a synchronous fast path for in-process transports (no Promise allocated when the write completes synchronously), a millisecond-cached timestamp, lazy formatting (each transport formats once — no wasted pre-format), and per-call work (level check, namespace resolution, redaction decision) served off pre-built caches. The result: logixia **beats pino on 5 of 6 real-world scenarios**, beats winston and bunyan across the board, and keeps **p99 latency at 1–3µs** with no tail spikes.
|
|
190
200
|
|
|
191
|
-
|
|
192
|
-
| ----------- | -------------------: | -----------------------: | ------------------: | -----------: |
|
|
193
|
-
| pino | 1,258,000 | 630,000 | 390,000 | 2.5–12µs |
|
|
194
|
-
| **logixia** | **840,000** | **696,000** | **654,000** | **4.8–10µs** |
|
|
195
|
-
| winston | 738,000 | 371,000 | 433,000 | 9–16µs |
|
|
201
|
+
Benchmarked against **pino, winston, and bunyan** — all writing to `/dev/null` (pure serialization + framework overhead, no disk/terminal cost). Node 20, Apple M-series; numbers are ops/sec, higher is better. Reproduce with `npm run benchmark`.
|
|
196
202
|
|
|
197
|
-
|
|
203
|
+
| Scenario | pino | **logixia** | winston | bunyan |
|
|
204
|
+
| ------------------------------ | --------: | ------------: | --------: | ------: |
|
|
205
|
+
| Simple string log | 3,220,000 | 2,769,000 | 1,577,000 | 707,000 |
|
|
206
|
+
| **Structured log (5 fields)** | 1,319,000 | **1,536,000** | 699,000 | 536,000 |
|
|
207
|
+
| **Error object logging** | 907,000 | **1,940,000** | 1,062,000 | 573,000 |
|
|
208
|
+
| **Child / per-request logger** | 1,093,000 | **1,436,000** | 321,000 | 380,000 |
|
|
209
|
+
| **Deep nested object** | 891,000 | **1,040,000** | 435,000 | 442,000 |
|
|
210
|
+
| **High-cardinality (12 flds)** | 651,000 | **1,027,000** | 316,000 | 404,000 |
|
|
198
211
|
|
|
199
|
-
|
|
212
|
+
**What this means:**
|
|
213
|
+
|
|
214
|
+
- ✅ **logixia is faster than pino on 5 of 6 scenarios** — including **+114% on error logging**, **+58% on high-cardinality**, **+31% on child loggers**, and **+16% on structured logs** — the shapes that dominate real production traffic.
|
|
215
|
+
- ✅ **logixia beats winston and bunyan in every scenario**, often by 2–3×, and avoids their tail-latency spikes (winston hit **3,038µs p99** on high-cardinality and **412µs** on deep objects; logixia stays **1–3µs p99** throughout).
|
|
216
|
+
- ⚖️ **pino still wins the trivial simple-string case** (−14%) because it writes synchronously straight to `process.stdout` — fast in a microbenchmark, but it blocks the event loop under real I/O and is exactly the path behind pino's open [flush-on-exit log-loss bug](#graceful-shutdown). logixia stays non-blocking and guarantees delivery, and pulls ahead the moment you log anything structured.
|
|
217
|
+
|
|
218
|
+
**Distinctive-feature throughput** (no cross-library equivalent — `npm run benchmark:features`):
|
|
219
|
+
|
|
220
|
+
| Operation | ops/sec | p99 |
|
|
221
|
+
| ---------------------------------------- | --------: | ----: |
|
|
222
|
+
| Wide event (accumulate 6 fields + emit) | 742,000 | 3.7µs |
|
|
223
|
+
| `safeStringify` (BigInt + circular) | 2,735,000 | 0.5µs |
|
|
224
|
+
| `decycle` + `retrocycle` round-trip | 1,003,000 | 1.3µs |
|
|
225
|
+
| Adaptive-sampling decision (hot path) | 1,950,000 | 0.9µs |
|
|
226
|
+
| Namespace child logging (`db.*` → debug) | 1,966,000 | 0.8µs |
|
|
227
|
+
|
|
228
|
+
Sampling and namespace resolution add **negligible overhead** (~µs), so you can keep them on in production.
|
|
229
|
+
|
|
230
|
+
To reproduce: `npm run benchmark` (core) and `npm run benchmark:features` (distinctive APIs).
|
|
200
231
|
|
|
201
232
|
---
|
|
202
233
|
|
|
@@ -1640,10 +1671,126 @@ app.post('/checkout', async (req, res) => {
|
|
|
1640
1671
|
|
|
1641
1672
|
---
|
|
1642
1673
|
|
|
1674
|
+
## OTLP logs export (OpenTelemetry-native)
|
|
1675
|
+
|
|
1676
|
+
logixia doesn't just _read_ the active OTel span (above) — it can _emit_ logs in the **OTLP/HTTP** format so they land in any OpenTelemetry backend (Grafana Loki, OpenObserve, Better Stack, Axiom, Datadog, SigNoz…) already correlated with traces. No `@opentelemetry/*` packages required (that JS API is still alpha) — the OTLP JSON is built directly, with proper `SeverityNumber` mapping (DEBUG=5, INFO=9, WARN=13, ERROR=17) and resource attributes.
|
|
1677
|
+
|
|
1678
|
+
```typescript
|
|
1679
|
+
import { OtlpLogTransport } from 'logixia';
|
|
1680
|
+
|
|
1681
|
+
const logger = createLogger({
|
|
1682
|
+
appName: 'api',
|
|
1683
|
+
transports: {
|
|
1684
|
+
custom: [
|
|
1685
|
+
new OtlpLogTransport({
|
|
1686
|
+
url: 'http://localhost:4318/v1/logs',
|
|
1687
|
+
serviceName: 'api',
|
|
1688
|
+
serviceVersion: '1.4.0',
|
|
1689
|
+
environment: 'production',
|
|
1690
|
+
headers: { 'x-api-key': process.env.OTLP_KEY! },
|
|
1691
|
+
}),
|
|
1692
|
+
],
|
|
1693
|
+
},
|
|
1694
|
+
});
|
|
1695
|
+
// Every log is exported as an OTel LogRecord with traceId/spanId for native
|
|
1696
|
+
// trace↔log correlation. Buffers drain on close() — no loss on shutdown.
|
|
1697
|
+
```
|
|
1698
|
+
|
|
1699
|
+
---
|
|
1700
|
+
|
|
1701
|
+
## Wide events / canonical log lines
|
|
1702
|
+
|
|
1703
|
+
Instead of scattering a request's story across many narrow log lines that you have to JOIN during an incident, emit **one dense, structured event per request** — the "canonical log line" (Stripe) / "wide event" (Honeycomb, _Observability 2.0_) pattern. Fields accumulate as the request flows through middleware and business logic via `AsyncLocalStorage`, then the whole event is emitted **once** — in a teardown path so it fires even on errors.
|
|
1704
|
+
|
|
1705
|
+
```typescript
|
|
1706
|
+
import { wideEventMiddleware, addEventFields } from 'logixia';
|
|
1707
|
+
|
|
1708
|
+
// One canonical line per request, auto-emitted on response finish/close:
|
|
1709
|
+
app.use(wideEventMiddleware(logger)); // pre-fills method, url, ip, status, duration
|
|
1710
|
+
|
|
1711
|
+
app.get('/checkout', (req, res) => {
|
|
1712
|
+
addEventFields({ userId: req.user.id, planTier: 'pro' }); // from anywhere
|
|
1713
|
+
addEventFields({ dbQueries: 4, cacheHit: true });
|
|
1714
|
+
res.json({ ok: true });
|
|
1715
|
+
// → ONE log line: { method, url, statusCode, durationMs, userId, planTier,
|
|
1716
|
+
// dbQueries, cacheHit, traceId } — no JOINs at query time
|
|
1717
|
+
});
|
|
1718
|
+
```
|
|
1719
|
+
|
|
1720
|
+
Or wrap any unit of work manually — the event is emitted once, even if the callback throws:
|
|
1721
|
+
|
|
1722
|
+
```typescript
|
|
1723
|
+
import { withWideEvent, addEventFields } from 'logixia';
|
|
1724
|
+
|
|
1725
|
+
await withWideEvent(logger, { job: 'reindex' }, async () => {
|
|
1726
|
+
addEventFields({ shard: 3 });
|
|
1727
|
+
await doWork(); // throws? → event still emitted with { error: true, errorMessage }
|
|
1728
|
+
});
|
|
1729
|
+
```
|
|
1730
|
+
|
|
1731
|
+
---
|
|
1732
|
+
|
|
1733
|
+
## Dynamic runtime reconfiguration
|
|
1734
|
+
|
|
1735
|
+
Change log levels in a **running** process — no restart — to chase a bug without raising global volume. This is the single most-requested feature across the Winston ([#1107](https://github.com/winstonjs/winston/issues/1107)) and Pino ([#206](https://github.com/pinojs/pino/issues/206)) trackers; logixia ships it first-class.
|
|
1736
|
+
|
|
1737
|
+
```typescript
|
|
1738
|
+
import { registerLevelSignal, createLevelControlHandler } from 'logixia';
|
|
1739
|
+
|
|
1740
|
+
// 1. Per-namespace level, live:
|
|
1741
|
+
logger.setNamespaceLevels({ 'db.*': 'debug', '*': 'info' }); // db.* → debug now
|
|
1742
|
+
|
|
1743
|
+
// 2. Cycle the global level with one signal (kill -USR2 <pid>):
|
|
1744
|
+
registerLevelSignal(logger); // info → debug → trace → … → info
|
|
1745
|
+
|
|
1746
|
+
// 3. Ops endpoint (mount behind your auth):
|
|
1747
|
+
app.all('/admin/log-level', createLevelControlHandler(logger));
|
|
1748
|
+
// GET → { level, namespaceLevels }
|
|
1749
|
+
// POST { "level": "debug", "namespaceLevels": { "db.*": "trace" } }
|
|
1750
|
+
```
|
|
1751
|
+
|
|
1752
|
+
---
|
|
1753
|
+
|
|
1754
|
+
## Adaptive sampling
|
|
1755
|
+
|
|
1756
|
+
On top of static / per-level / trace-consistent sampling + a token-bucket rate cap, logixia can **boost the sample rate automatically during an incident** — so high-volume cost control never costs you the logs that matter most. When the windowed error rate crosses a threshold, sampling lifts toward 1.0; in steady state it relaxes back to the base rate.
|
|
1757
|
+
|
|
1758
|
+
```typescript
|
|
1759
|
+
const logger = createLogger({
|
|
1760
|
+
appName: 'api',
|
|
1761
|
+
sampling: {
|
|
1762
|
+
rate: 0.1, // keep 10% in steady state
|
|
1763
|
+
adaptive: {
|
|
1764
|
+
errorRateThreshold: 0.05, // ≥5% errors in the window…
|
|
1765
|
+
boostRate: 1.0, // …keep everything until it subsides
|
|
1766
|
+
windowMs: 10_000,
|
|
1767
|
+
},
|
|
1768
|
+
},
|
|
1769
|
+
});
|
|
1770
|
+
```
|
|
1771
|
+
|
|
1772
|
+
---
|
|
1773
|
+
|
|
1774
|
+
## Robust serialization
|
|
1775
|
+
|
|
1776
|
+
logixia never throws while serializing a log payload. Circular references become `[Circular]`, and — going beyond what Winston/Pino do — **BigInt** is handled (raw `JSON.stringify` throws on it) and you can opt into **true round-trippable decycling** for shared/circular graphs:
|
|
1777
|
+
|
|
1778
|
+
```typescript
|
|
1779
|
+
import { safeStringify, decycleValue, retrocycle } from 'logixia';
|
|
1780
|
+
|
|
1781
|
+
safeStringify({ id: 9007199254740993n, self: obj }); // BigInt + cycle safe
|
|
1782
|
+
const json = safeStringify(graph, { decycle: true }); // $ref pointers, not [Circular]
|
|
1783
|
+
const restored = retrocycle(JSON.parse(json)); // shared refs reconstructed
|
|
1784
|
+
```
|
|
1785
|
+
|
|
1786
|
+
---
|
|
1787
|
+
|
|
1643
1788
|
## Graceful shutdown
|
|
1644
1789
|
|
|
1645
1790
|
Ensures all buffered log entries are flushed to every transport before the process exits. Critical for database and analytics transports that batch writes.
|
|
1646
1791
|
|
|
1792
|
+
> **Reliability guarantee — no log loss on shutdown.** The most painful, still-open bug in the most popular Node logger is exactly this: Pino's [#1705](https://github.com/pinojs/pino/issues/1705) ("Logs are not flushed, missing log entries after `process.exit()`") has been open since 2023, with its maintainer noting a race condition in the transport flush path that "I won't be able to fix it anytime soon." It recurs across [#542](https://github.com/pinojs/pino/issues/542), [#1774](https://github.com/pinojs/pino/issues/1774), [#1889](https://github.com/pinojs/pino/issues/1889), and [#2054](https://github.com/pinojs/pino/issues/2054). logixia is built the other way around: **every** batching/async transport (database, analytics, CloudWatch/GCP/Azure, worker-thread, browser, OTLP) drains its buffer synchronously on `close()` with bounded retry, and the SIGTERM/SIGINT handler is guarded against concurrent signals so a second Ctrl+C can't truncate the flush. Each guarantee is covered by a regression test.
|
|
1793
|
+
|
|
1647
1794
|
The simplest approach is to set `gracefulShutdown: true` in config — logixia registers SIGTERM and SIGINT handlers automatically:
|
|
1648
1795
|
|
|
1649
1796
|
```typescript
|