logixia 2.0.0 → 2.0.6
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 +172 -2597
- package/dist/async-context-2mbUKhcl.mjs +132 -0
- package/dist/async-context-2mbUKhcl.mjs.map +1 -0
- package/dist/async-context-DUFMDYAr.js +148 -0
- package/dist/async-context-DUFMDYAr.js.map +1 -0
- package/dist/{azure-monitor.transport-CrvfHd9C.js → azure-monitor.transport-Bu4S2I2q.js} +32 -20
- package/dist/azure-monitor.transport-Bu4S2I2q.js.map +1 -0
- package/dist/{azure-monitor.transport-Cn_3Usvu.d.ts → azure-monitor.transport-Bvf7Xegt.d.ts} +3 -6
- package/dist/azure-monitor.transport-Bvf7Xegt.d.ts.map +1 -0
- package/dist/{azure-monitor.transport-CHnjVRF-.mjs → azure-monitor.transport-C1RWAumT.mjs} +27 -15
- package/dist/azure-monitor.transport-C1RWAumT.mjs.map +1 -0
- package/dist/{azure-monitor.transport-D7Da4RAb.d.mts → azure-monitor.transport-CbSHOnNX.d.mts} +3 -6
- package/dist/azure-monitor.transport-CbSHOnNX.d.mts.map +1 -0
- package/dist/azure.d.mts +1 -2
- package/dist/azure.d.ts +1 -2
- package/dist/azure.js +3 -6
- package/dist/azure.mjs +1 -6
- package/dist/{batch-buffer-PGvSgbmd.mjs → batch-buffer-BvXLDPlV.mjs} +21 -7
- package/dist/batch-buffer-BvXLDPlV.mjs.map +1 -0
- package/dist/{batch-buffer-BY-mRWIK.js → batch-buffer-C7Am4Mw-.js} +53 -40
- package/dist/batch-buffer-C7Am4Mw-.js.map +1 -0
- package/dist/browser.d.mts +47 -27
- package/dist/browser.d.mts.map +1 -1
- package/dist/browser.d.ts +47 -27
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +464 -62
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +465 -65
- package/dist/browser.mjs.map +1 -1
- package/dist/{builder-0gEzRopq.mjs → builder-DhNhvOkt.mjs} +307 -246
- package/dist/builder-DhNhvOkt.mjs.map +1 -0
- package/dist/{builder-D44QktIo.js → builder-yFvJrz7o.js} +435 -399
- package/dist/builder-yFvJrz7o.js.map +1 -0
- package/dist/cli/index.js +216 -125
- package/dist/cloudwatch.d.mts +1 -2
- package/dist/cloudwatch.d.ts +1 -2
- package/dist/cloudwatch.js +3 -7
- package/dist/cloudwatch.mjs +1 -7
- package/dist/{cloudwatch.transport-DsEq633o.d.ts → cloudwatch.transport-CQyT6U91.d.ts} +2 -6
- package/dist/cloudwatch.transport-CQyT6U91.d.ts.map +1 -0
- package/dist/{cloudwatch.transport-BO1ahxw-.js → cloudwatch.transport-Ch1Uae-0.js} +61 -36
- package/dist/cloudwatch.transport-Ch1Uae-0.js.map +1 -0
- package/dist/{cloudwatch.transport-UlyZFWMn.d.mts → cloudwatch.transport-CjVrI6S4.d.mts} +2 -6
- package/dist/cloudwatch.transport-CjVrI6S4.d.mts.map +1 -0
- package/dist/{cloudwatch.transport-DZ6ies7g.mjs → cloudwatch.transport-DZqF2xZO.mjs} +54 -28
- package/dist/cloudwatch.transport-DZqF2xZO.mjs.map +1 -0
- package/dist/{coerce.utils-Cu6IUTgX.mjs → coerce.utils-B_rEudCn.mjs} +3 -4
- package/dist/{coerce.utils-Cu6IUTgX.mjs.map → coerce.utils-B_rEudCn.mjs.map} +1 -1
- package/dist/{coerce.utils-B-LilukN.js → coerce.utils-C1C5sIX0.js} +8 -10
- package/dist/{coerce.utils-B-LilukN.js.map → coerce.utils-C1C5sIX0.js.map} +1 -1
- package/dist/correlation.d.mts +15 -69
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +15 -69
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +69 -20
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +67 -17
- package/dist/correlation.mjs.map +1 -1
- package/dist/{entry-fields-BvOLFlJ4.js → entry-fields-BSLkzw18.js} +16 -13
- package/dist/entry-fields-BSLkzw18.js.map +1 -0
- package/dist/{entry-fields-BJkuPnIg.mjs → entry-fields-J1OBi24o.mjs} +11 -7
- package/dist/entry-fields-J1OBi24o.mjs.map +1 -0
- package/dist/gcp.d.mts +1 -2
- package/dist/gcp.d.ts +1 -2
- package/dist/gcp.js +3 -7
- package/dist/gcp.mjs +1 -7
- package/dist/{gcp.transport-B7oc97sG.d.ts → gcp.transport-BCIvdFV-.d.ts} +5 -8
- package/dist/gcp.transport-BCIvdFV-.d.ts.map +1 -0
- package/dist/{gcp.transport-GrJurKo8.d.mts → gcp.transport-D9SdLPwP.d.mts} +5 -8
- package/dist/gcp.transport-D9SdLPwP.d.mts.map +1 -0
- package/dist/{gcp.transport-DBX5TPIy.mjs → gcp.transport-DVCmnA3b.mjs} +59 -16
- package/dist/gcp.transport-DVCmnA3b.mjs.map +1 -0
- package/dist/{gcp.transport-CHV8MGgz.js → gcp.transport-Dy2Lf8FI.js} +64 -22
- package/dist/gcp.transport-Dy2Lf8FI.js.map +1 -0
- package/dist/http-shapes-BnBkJcid.d.mts +32 -0
- package/dist/http-shapes-BnBkJcid.d.mts.map +1 -0
- package/dist/http-shapes-BnBkJcid.d.ts +32 -0
- package/dist/http-shapes-BnBkJcid.d.ts.map +1 -0
- package/dist/{index-3mw5gaCw.d.mts → index-BSTtFcQ6.d.mts} +142 -13
- package/dist/index-BSTtFcQ6.d.mts.map +1 -0
- package/dist/{index-DhIoploR.d.ts → index-CFDGkI8n.d.ts} +142 -13
- package/dist/index-CFDGkI8n.d.ts.map +1 -0
- package/dist/index.d.mts +114 -217
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +113 -216
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +182 -279
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +160 -256
- package/dist/index.mjs.map +1 -1
- package/dist/internal-log-Bg4V1cPg.mjs +170 -0
- package/dist/internal-log-Bg4V1cPg.mjs.map +1 -0
- package/dist/internal-log-By4Govmu.js +223 -0
- package/dist/internal-log-By4Govmu.js.map +1 -0
- package/dist/{logitron-logger-UVeN-mQm.d.ts → logitron-logger-D22Yb61K.d.mts} +121 -100
- package/dist/logitron-logger-D22Yb61K.d.mts.map +1 -0
- package/dist/{logitron-logger-BFDSO8Io.d.mts → logitron-logger-D_MYYrQt.d.ts} +121 -100
- package/dist/logitron-logger-D_MYYrQt.d.ts.map +1 -0
- package/dist/middleware.d.mts +13 -28
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +13 -28
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +20 -81
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +17 -79
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +37 -48
- package/dist/nest.d.mts.map +1 -1
- package/dist/nest.d.ts +37 -48
- package/dist/nest.d.ts.map +1 -1
- package/dist/nest.js +124 -135
- package/dist/nest.js.map +1 -1
- package/dist/nest.mjs +69 -78
- package/dist/nest.mjs.map +1 -1
- package/dist/{transport.manager-BiuCAJd2.mjs → otel-Dt5riXuE.mjs} +275 -117
- package/dist/otel-Dt5riXuE.mjs.map +1 -0
- package/dist/{transport.manager-CXbKKdVE.js → otel-DyeH6DVR.js} +373 -178
- package/dist/otel-DyeH6DVR.js.map +1 -0
- package/dist/own-property-BWkibepN.js +40 -0
- package/dist/own-property-BWkibepN.js.map +1 -0
- package/dist/own-property-CjFw9qwZ.mjs +29 -0
- package/dist/own-property-CjFw9qwZ.mjs.map +1 -0
- package/dist/{trace.utils-CvXgaiQT.mjs → request-trace-BgNykH9M.mjs} +40 -9
- package/dist/request-trace-BgNykH9M.mjs.map +1 -0
- package/dist/{trace.utils-DpBOLDtq.js → request-trace-ByujsUZk.js} +117 -77
- package/dist/request-trace-ByujsUZk.js.map +1 -0
- package/dist/rolldown-runtime-D3PmRqjj.mjs +4 -0
- package/dist/{safe-stringify-DJ66tW5I.mjs → safe-stringify-B5p5ktFe.mjs} +39 -15
- package/dist/safe-stringify-B5p5ktFe.mjs.map +1 -0
- package/dist/{safe-stringify-DWo3aZp_.js → safe-stringify-J3sh-7YM.js} +59 -36
- package/dist/safe-stringify-J3sh-7YM.js.map +1 -0
- package/dist/{search-C4V0OMEn.js → search-CRjhHxe9.js} +319 -92
- package/dist/search-CRjhHxe9.js.map +1 -0
- package/dist/{search-CNh9uVwR.d.ts → search-DA3ft3X5.d.mts} +5 -22
- package/dist/search-DA3ft3X5.d.mts.map +1 -0
- package/dist/{search-BFbu_oVn.mjs → search-DAXKE-P9.mjs} +289 -62
- package/dist/search-DAXKE-P9.mjs.map +1 -0
- package/dist/{search-DXANsQ2n.d.mts → search-DEl3Pe47.d.ts} +5 -22
- package/dist/search-DEl3Pe47.d.ts.map +1 -0
- package/dist/search.d.mts +2 -4
- package/dist/search.d.ts +2 -4
- package/dist/search.js +3 -4
- package/dist/search.mjs +1 -4
- package/dist/secret-config-CmK2wVkr.js +272 -0
- package/dist/secret-config-CmK2wVkr.js.map +1 -0
- package/dist/secret-config-Dhb6pqTU.mjs +210 -0
- package/dist/secret-config-Dhb6pqTU.mjs.map +1 -0
- package/dist/{slack.transport-CXjQYOes.d.ts → slack.transport-BDyENuR0.d.mts} +56 -9
- package/dist/slack.transport-BDyENuR0.d.mts.map +1 -0
- package/dist/{slack.transport-Bzw7sucI.mjs → slack.transport-BTEwsREY.mjs} +263 -31
- package/dist/slack.transport-BTEwsREY.mjs.map +1 -0
- package/dist/{slack.transport-BZzoXDPn.d.mts → slack.transport-C6E0l4kw.d.ts} +56 -9
- package/dist/slack.transport-C6E0l4kw.d.ts.map +1 -0
- package/dist/{slack.transport-BfJRouB2.js → slack.transport-UbYvOdKx.js} +286 -42
- package/dist/slack.transport-UbYvOdKx.js.map +1 -0
- package/dist/testing.d.mts +13 -11
- package/dist/testing.d.mts.map +1 -1
- package/dist/testing.d.ts +13 -11
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +41 -7
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +40 -6
- package/dist/testing.mjs.map +1 -1
- package/dist/{transport.types-CGoHpWQR.d.ts → transport.types-CzJq4IrF.d.mts} +14 -5
- package/dist/transport.types-CzJq4IrF.d.mts.map +1 -0
- package/dist/{transport.types-BMnurPcc.d.mts → transport.types-CzJq4IrF.d.ts} +14 -5
- package/dist/transport.types-CzJq4IrF.d.ts.map +1 -0
- package/dist/transports.d.mts +39 -41
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +39 -41
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +19 -22
- package/dist/transports.mjs +7 -13
- package/dist/worker-entry.js +438 -107
- package/dist/worker.d.mts +1 -2
- package/dist/worker.d.ts +1 -2
- package/dist/worker.js +3 -4
- package/dist/worker.mjs +1 -4
- package/dist/{worker.transport-JvGPog3C.d.mts → worker.transport-B9mCQSM1.d.ts} +7 -6
- package/dist/worker.transport-B9mCQSM1.d.ts.map +1 -0
- package/dist/{worker.transport-CdzSZJmw.d.ts → worker.transport-Bl8lFRck.d.mts} +7 -6
- package/dist/worker.transport-Bl8lFRck.d.mts.map +1 -0
- package/dist/{worker.transport-lPxO2MVL.mjs → worker.transport-BmmOKb1-.mjs} +43 -10
- package/dist/worker.transport-BmmOKb1-.mjs.map +1 -0
- package/dist/{worker.transport-BdPxu3PN.js → worker.transport-DxxbqQCG.js} +48 -15
- package/dist/worker.transport-DxxbqQCG.js.map +1 -0
- package/package.json +51 -75
- package/dist/async-context-BzJrbfQs.mjs +0 -72
- package/dist/async-context-BzJrbfQs.mjs.map +0 -1
- package/dist/async-context-DyQQYq3I.js +0 -92
- package/dist/async-context-DyQQYq3I.js.map +0 -1
- package/dist/azure-monitor.transport-CHnjVRF-.mjs.map +0 -1
- package/dist/azure-monitor.transport-Cn_3Usvu.d.ts.map +0 -1
- package/dist/azure-monitor.transport-CrvfHd9C.js.map +0 -1
- package/dist/azure-monitor.transport-D7Da4RAb.d.mts.map +0 -1
- package/dist/batch-buffer-BY-mRWIK.js.map +0 -1
- package/dist/batch-buffer-PGvSgbmd.mjs.map +0 -1
- package/dist/builder-0gEzRopq.mjs.map +0 -1
- package/dist/builder-D44QktIo.js.map +0 -1
- package/dist/chunk-DLSkYcPk.mjs +0 -7
- package/dist/chunk-nOFOJqeH.js +0 -30
- package/dist/cloudwatch.transport-BO1ahxw-.js.map +0 -1
- package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +0 -1
- package/dist/cloudwatch.transport-DsEq633o.d.ts.map +0 -1
- package/dist/cloudwatch.transport-UlyZFWMn.d.mts.map +0 -1
- package/dist/entry-fields-BJkuPnIg.mjs.map +0 -1
- package/dist/entry-fields-BvOLFlJ4.js.map +0 -1
- package/dist/gcp.transport-B7oc97sG.d.ts.map +0 -1
- package/dist/gcp.transport-CHV8MGgz.js.map +0 -1
- package/dist/gcp.transport-DBX5TPIy.mjs.map +0 -1
- package/dist/gcp.transport-GrJurKo8.d.mts.map +0 -1
- package/dist/index-3mw5gaCw.d.mts.map +0 -1
- package/dist/index-DhIoploR.d.ts.map +0 -1
- package/dist/internal-log-BUJYMZbE.js +0 -70
- package/dist/internal-log-BUJYMZbE.js.map +0 -1
- package/dist/internal-log-Cm8SOWSV.mjs +0 -52
- package/dist/internal-log-Cm8SOWSV.mjs.map +0 -1
- package/dist/logitron-logger-BFDSO8Io.d.mts.map +0 -1
- package/dist/logitron-logger-UVeN-mQm.d.ts.map +0 -1
- package/dist/safe-stringify-DJ66tW5I.mjs.map +0 -1
- package/dist/safe-stringify-DWo3aZp_.js.map +0 -1
- package/dist/search-BFbu_oVn.mjs.map +0 -1
- package/dist/search-C4V0OMEn.js.map +0 -1
- package/dist/search-CNh9uVwR.d.ts.map +0 -1
- package/dist/search-DXANsQ2n.d.mts.map +0 -1
- package/dist/slack.transport-BZzoXDPn.d.mts.map +0 -1
- package/dist/slack.transport-BfJRouB2.js.map +0 -1
- package/dist/slack.transport-Bzw7sucI.mjs.map +0 -1
- package/dist/slack.transport-CXjQYOes.d.ts.map +0 -1
- package/dist/trace.utils-CvXgaiQT.mjs.map +0 -1
- package/dist/trace.utils-DpBOLDtq.js.map +0 -1
- package/dist/transport.manager-BiuCAJd2.mjs.map +0 -1
- package/dist/transport.manager-CXbKKdVE.js.map +0 -1
- package/dist/transport.types-BMnurPcc.d.mts.map +0 -1
- package/dist/transport.types-CGoHpWQR.d.ts.map +0 -1
- package/dist/worker.transport-BdPxu3PN.js.map +0 -1
- package/dist/worker.transport-CdzSZJmw.d.ts.map +0 -1
- package/dist/worker.transport-JvGPog3C.d.mts.map +0 -1
- package/dist/worker.transport-lPxO2MVL.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,2754 +1,329 @@
|
|
|
1
1
|
# logixia
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<img src="https://raw.githubusercontent.com/Logixia/logixia/main/.github/assets/logixia-banner.png" alt="logixia: the async-first TypeScript logger
|
|
4
|
+
<img src="https://raw.githubusercontent.com/Logixia/logixia/main/.github/assets/logixia-banner.png" alt="logixia: the async-first TypeScript logger" width="100%"/>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
9
|
-
Wide events · OpenTelemetry (OTLP) · Runtime log levels · Redaction · Adaptive sampling<br/>
|
|
10
|
-
NestJS · Express · Fastify · Database · Cloud · Tracing · Prometheus · Browser
|
|
8
|
+
<strong>An async-first TypeScript logger with request tracing, redaction, OpenTelemetry export and the transports you usually have to assemble yourself.</strong>
|
|
11
9
|
</p>
|
|
12
10
|
|
|
13
11
|
<p align="center">
|
|
14
12
|
<a href="https://www.npmjs.com/package/logixia"><img src="https://img.shields.io/npm/v/logixia" alt="npm version"/></a>
|
|
15
13
|
<a href="https://www.npmjs.com/package/logixia"><img src="https://img.shields.io/npm/dm/logixia" alt="npm downloads"/></a>
|
|
16
|
-
<a href="https://bundlephobia.com/package/logixia"><img src="https://img.shields.io/bundlephobia/minzip/logixia" alt="bundle size"/></a>
|
|
17
14
|
<a href="https://github.com/Logixia/logixia/actions/workflows/ci.yml"><img src="https://github.com/Logixia/logixia/actions/workflows/ci.yml/badge.svg" alt="CI"/></a>
|
|
18
|
-
<a href="https://
|
|
15
|
+
<a href="https://www.npmjs.com/package/logixia"><img src="https://img.shields.io/badge/npm-provenance-brightgreen" alt="npm provenance"/></a>
|
|
16
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/npm/l/logixia" alt="MIT license"/></a>
|
|
19
17
|
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.0%2B-blue" alt="TypeScript"/></a>
|
|
20
|
-
<a href="https://logixia.github.io/logixia/"><img src="https://img.shields.io/badge/website-logixia.github.io-a855f7" alt="website"/></a>
|
|
21
|
-
<a href="#sponsor"><img src="https://img.shields.io/badge/sponsor-%E2%99%A5-a855f7" alt="sponsor"/></a>
|
|
22
18
|
</p>
|
|
23
19
|
|
|
24
20
|
<p align="center">
|
|
25
|
-
<a href="https://logixia.github.io/logixia/"><strong>
|
|
26
|
-
<a href="https://github.
|
|
27
|
-
<a href="https://
|
|
21
|
+
<a href="https://logixia.github.io/logixia/"><strong>Documentation</strong></a> ·
|
|
22
|
+
<a href="https://logixia.github.io/logixia/guide/getting-started">Getting started</a> ·
|
|
23
|
+
<a href="https://logixia.github.io/logixia/guide/migration-v2">Upgrading to 2.x</a> ·
|
|
28
24
|
<a href="https://github.com/Logixia/logixia/issues">Issues</a>
|
|
29
25
|
</p>
|
|
30
26
|
|
|
31
27
|
---
|
|
32
28
|
|
|
33
|
-
##
|
|
29
|
+
## Why logixia
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
# The pino route:
|
|
37
|
-
npm install pino pino-pretty pino-roll pino-redact pino-nestjs pino-http
|
|
38
|
-
|
|
39
|
-
# The winston route:
|
|
40
|
-
npm install winston winston-daily-rotate-file
|
|
41
|
-
# ...then wire 4 separate config objects
|
|
42
|
-
# ...then discover there's no built-in DB transport
|
|
43
|
-
# ...then discover request tracing is manual
|
|
44
|
-
# ...then discover both block your event loop under I/O pressure
|
|
45
|
-
|
|
46
|
-
# Or:
|
|
47
|
-
npm install logixia
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
logixia ships **console + file rotation + database + request tracing + NestJS module + field redaction + log search + OpenTelemetry + plugin API + Prometheus metrics + visual TUI explorer** in one package — non-blocking on every transport, zero extra installs.
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import { createLogger } from 'logixia';
|
|
54
|
-
|
|
55
|
-
const logger = createLogger({
|
|
56
|
-
appName: 'api',
|
|
57
|
-
environment: 'production',
|
|
58
|
-
transports: {
|
|
59
|
-
console: { format: 'json' },
|
|
60
|
-
file: { filename: 'app.log', dirname: './logs', maxSize: '50MB' },
|
|
61
|
-
database: { type: 'postgresql', host: 'localhost', database: 'appdb', table: 'logs' },
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
await logger.info('Server started', { port: 3000 });
|
|
66
|
-
// Writes to console + file + postgres simultaneously. Non-blocking. Done.
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## Table of Contents
|
|
72
|
-
|
|
73
|
-
- [Why logixia?](#why-logixia)
|
|
74
|
-
- [Feature comparison](#feature-comparison)
|
|
75
|
-
- [Performance](#performance)
|
|
76
|
-
- [Installation](#installation)
|
|
77
|
-
- [Quick start](#quick-start)
|
|
78
|
-
- [Core concepts](#core-concepts)
|
|
79
|
-
- [Log levels](#log-levels)
|
|
80
|
-
- [Structured logging](#structured-logging)
|
|
81
|
-
- [Child loggers](#child-loggers)
|
|
82
|
-
- [Adaptive log level](#adaptive-log-level)
|
|
83
|
-
- [Per-namespace log levels](#per-namespace-log-levels)
|
|
84
|
-
- [Transports](#transports)
|
|
85
|
-
- [Console](#console)
|
|
86
|
-
- [File with rotation](#file-with-rotation)
|
|
87
|
-
- [Database](#database)
|
|
88
|
-
- [Analytics](#analytics)
|
|
89
|
-
- [Multiple transports simultaneously](#multiple-transports-simultaneously)
|
|
90
|
-
- [Custom transport](#custom-transport)
|
|
91
|
-
- [Cloud adapters](#cloud-adapters)
|
|
92
|
-
- [AWS CloudWatch](#aws-cloudwatch)
|
|
93
|
-
- [Google Cloud Logging](#google-cloud-logging)
|
|
94
|
-
- [Azure Monitor](#azure-monitor)
|
|
95
|
-
- [Request tracing](#request-tracing)
|
|
96
|
-
- [Core trace utilities](#core-trace-utilities)
|
|
97
|
-
- [Express / Fastify middleware](#express--fastify-middleware)
|
|
98
|
-
- [NestJS trace middleware](#nestjs-trace-middleware)
|
|
99
|
-
- [Kafka trace interceptor](#kafka-trace-interceptor)
|
|
100
|
-
- [WebSocket trace interceptor](#websocket-trace-interceptor)
|
|
101
|
-
- [NestJS integration](#nestjs-integration)
|
|
102
|
-
- [@LogMethod decorator](#logmethod-decorator)
|
|
103
|
-
- [LogixiaExceptionFilter](#logixiaexceptionfilter)
|
|
104
|
-
- [Correlation ID propagation](#correlation-id-propagation)
|
|
105
|
-
- [Express middleware](#correlation-express-middleware)
|
|
106
|
-
- [Fastify hook](#correlation-fastify-hook)
|
|
107
|
-
- [Outbound fetch / axios](#outbound-fetch--axios)
|
|
108
|
-
- [Kafka / SQS helpers](#kafka--sqs-helpers)
|
|
109
|
-
- [Browser support](#browser-support)
|
|
110
|
-
- [Log redaction](#log-redaction)
|
|
111
|
-
- [Timer API](#timer-api)
|
|
112
|
-
- [Field management](#field-management)
|
|
113
|
-
- [Transport level control](#transport-level-control)
|
|
114
|
-
- [Transport filter predicate](#transport-filter-predicate)
|
|
115
|
-
- [Log search](#log-search)
|
|
116
|
-
- [OpenTelemetry](#opentelemetry)
|
|
117
|
-
- [OTLP logs export](#otlp-logs-export-opentelemetry-native)
|
|
118
|
-
- [Wide events / canonical log lines](#wide-events--canonical-log-lines)
|
|
119
|
-
- [Dynamic runtime reconfiguration](#dynamic-runtime-reconfiguration)
|
|
120
|
-
- [Adaptive sampling](#adaptive-sampling)
|
|
121
|
-
- [Robust serialization](#robust-serialization)
|
|
122
|
-
- [Graceful shutdown](#graceful-shutdown)
|
|
123
|
-
- [Plugin / extension API](#plugin--extension-api)
|
|
124
|
-
- [Writing a plugin](#writing-a-plugin)
|
|
125
|
-
- [Registering plugins globally](#registering-plugins-globally)
|
|
126
|
-
- [Per-logger plugins](#per-logger-plugins)
|
|
127
|
-
- [Cancelling a log entry](#cancelling-a-log-entry)
|
|
128
|
-
- [Metrics → Prometheus](#metrics--prometheus)
|
|
129
|
-
- [Quick start (counters)](#quick-start-counters)
|
|
130
|
-
- [Histograms](#histograms)
|
|
131
|
-
- [Gauges](#gauges)
|
|
132
|
-
- [Exposing the /metrics endpoint](#exposing-the-metrics-endpoint)
|
|
133
|
-
- [Metric configuration reference](#metric-configuration-reference)
|
|
134
|
-
- [Logger instance API](#logger-instance-api)
|
|
135
|
-
- [CLI tool](#cli-tool)
|
|
136
|
-
- [explore — Visual TUI log explorer](#explore--visual-tui-log-explorer)
|
|
137
|
-
- [Configuration reference](#configuration-reference)
|
|
138
|
-
- [Sponsor](#sponsor)
|
|
139
|
-
- [Contributing](#contributing)
|
|
140
|
-
- [License](#license)
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Why logixia?
|
|
145
|
-
|
|
146
|
-
`console.log` doesn't scale. `pino` is fast but leaves database persistence, NestJS integration, log search, and field redaction entirely to plugins. `winston` is flexible but synchronous and requires substantial boilerplate to get production-ready.
|
|
147
|
-
|
|
148
|
-
logixia takes a different approach: **everything ships built-in, and nothing blocks your event loop.**
|
|
149
|
-
|
|
150
|
-
- **Async by design** — every log call is non-blocking, even to file and database transports
|
|
151
|
-
- **Built-in database transports** — PostgreSQL, MySQL, MongoDB, SQLite with zero extra drivers
|
|
152
|
-
- **Cloud adapters** — AWS CloudWatch (EMF), Google Cloud Logging, and Azure Monitor out of the box
|
|
153
|
-
- **NestJS module** — plug in with `LogixiaLoggerModule.forRoot()`, inject anywhere in the DI tree; `@LogMethod()` for auto-logging method entry/exit
|
|
154
|
-
- **File rotation** — `maxSize`, `maxFiles`, gzip archive, time-based rotation — no extra packages needed
|
|
155
|
-
- **Log search** — query your in-memory log store without shipping to an external service
|
|
156
|
-
- **Field redaction** — mask passwords, tokens, and PII before they touch any transport; supports dot-notation paths and regex patterns
|
|
157
|
-
- **Request tracing** — `AsyncLocalStorage`-based trace propagation with no manual thread-locals; includes Kafka and WebSocket interceptors
|
|
158
|
-
- **Correlation ID propagation** — auto-generate and forward `X-Correlation-ID` through `fetch`, axios, Kafka, and SQS across microservice boundaries
|
|
159
|
-
- **Browser support** — tree-shakeable `logixia/browser` entry point with console and remote batch transports; no Node.js built-ins
|
|
160
|
-
- **OpenTelemetry** — W3C `traceparent` and `tracestate` support, zero extra dependencies
|
|
161
|
-
- **Multi-transport** — write to console, file, and database concurrently with one log call
|
|
162
|
-
- **TypeScript-first** — typed log entries, typed metadata, custom-level IntelliSense throughout
|
|
163
|
-
- **Adaptive log level** — auto-configures based on `NODE_ENV` and CI environment
|
|
164
|
-
- **Custom transports** — ship to Slack, PagerDuty, S3, or anywhere else via a simple interface
|
|
165
|
-
- **Plugin / extension API** — lifecycle hooks (`onInit`, `onLog`, `onError`, `onShutdown`); plugins can mutate or cancel log entries; register globally or per-logger
|
|
166
|
-
- **Prometheus metrics** — turn log events into counters, histograms, and gauges with zero code; expose `GET /metrics` in Prometheus text format; works with any HTTP framework
|
|
167
|
-
- **Visual TUI explorer** — `logixia explore` opens a full-screen terminal log browser with real-time search, level filtering, syntax-highlighted JSON detail panel, stack trace rendering, and one-key export to JSON / CSV / NDJSON
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## Feature comparison
|
|
172
|
-
|
|
173
|
-
| Feature | **logixia** | pino | winston | bunyan |
|
|
174
|
-
| ------------------------------------- | :---------: | :---------: | :-----------------------: | :-----: |
|
|
175
|
-
| TypeScript-first | yes | partial | partial | partial |
|
|
176
|
-
| Async / non-blocking writes | yes | no | no | no |
|
|
177
|
-
| NestJS module (built-in) | yes | no | no | no |
|
|
178
|
-
| Database transports (built-in) | yes | no | no | no |
|
|
179
|
-
| Cloud transports (CW, GCP, Azure) | yes | no | no | no |
|
|
180
|
-
| File rotation (built-in) | yes | pino-roll | winston-daily-rotate-file | no |
|
|
181
|
-
| Multi-transport concurrent | yes | no | yes | no |
|
|
182
|
-
| Log search | yes | no | no | no |
|
|
183
|
-
| Field + message redaction (built-in) | yes | pino-redact | no | no |
|
|
184
|
-
| Request tracing (AsyncLocalStorage) | yes | no | no | no |
|
|
185
|
-
| Kafka + WebSocket trace interceptors | yes | no | no | no |
|
|
186
|
-
| Correlation ID propagation | yes | no | no | no |
|
|
187
|
-
| Browser / Edge / Bun / Deno support | yes | partial | no | no |
|
|
188
|
-
| OpenTelemetry / W3C headers | yes | no | no | no |
|
|
189
|
-
| **OTLP logs export (OTel-native)** | **yes** | transport | no | no |
|
|
190
|
-
| **Wide events / canonical log lines** | **yes** | no | no | no |
|
|
191
|
-
| **Runtime log-level reconfig** | **yes** | external | no | no |
|
|
192
|
-
| **Adaptive (anomaly) sampling** | **yes** | no | no | no |
|
|
193
|
-
| Graceful shutdown / flush (no loss) | yes | partial | no | no |
|
|
194
|
-
| Custom log levels | yes | yes | yes | yes |
|
|
195
|
-
| Adaptive log level (NODE_ENV) | yes | no | no | no |
|
|
196
|
-
| Plugin / extension API | yes | no | no | no |
|
|
197
|
-
| Prometheus metrics extraction | yes | no | no | no |
|
|
198
|
-
| Visual TUI log explorer | yes | no | no | no |
|
|
199
|
-
| Actively maintained | yes | yes | yes | no |
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
## Performance
|
|
204
|
-
|
|
205
|
-
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), and per-call work (level check, namespace resolution, redaction decision) served off pre-built caches.
|
|
206
|
-
|
|
207
|
-
Every library below writes the **same JSON output** to a null sink, so this measures serialization and framework overhead, not disk or terminal speed. Node 22, Apple Silicon, ops/sec (higher is better). Reproduce with `npm run benchmark`.
|
|
208
|
-
|
|
209
|
-
| Scenario | pino | logixia (json) | winston |
|
|
210
|
-
| ---------------------------- | --------: | -------------: | --------: |
|
|
211
|
-
| Simple string log | 3,396,000 | 1,804,000 | 1,533,000 |
|
|
212
|
-
| Structured log (5 fields) | 1,308,000 | 931,000 | 729,000 |
|
|
213
|
-
| Error object logging | 1,192,000 | 1,587,000 | 1,078,000 |
|
|
214
|
-
| Child / per-request logger | 1,134,000 | 892,000 | 494,000 |
|
|
215
|
-
| Deep nested object | 1,327,000 | 1,023,000 | 549,000 |
|
|
216
|
-
| High-cardinality (12 fields) | 734,000 | 525,000 | 400,000 |
|
|
31
|
+
Most Node loggers give you a fast `info()` and leave the rest to you. logixia keeps the call simple and ships the parts you end up writing anyway:
|
|
217
32
|
|
|
218
|
-
**
|
|
33
|
+
- **Request tracing built in.** Trace IDs travel through `await` via `AsyncLocalStorage`. Express and Fastify helpers read `traceparent`, `x-request-id` and friends, and every log line inside the request carries the ID.
|
|
34
|
+
- **Redaction before dispatch.** Mask paths (`*.password`, `req.headers.authorization`) and regex patterns once, before any transport sees the entry. Transport credentials are also kept out of serialized output and error text.
|
|
35
|
+
- **Transports for real destinations.** Console, rotating files, PostgreSQL, MySQL, MongoDB, SQLite, CloudWatch, Google Cloud Logging, Azure Monitor, Datadog, OTLP, Slack, Discord, and a worker-thread wrapper. Several can run on one log call.
|
|
36
|
+
- **OpenTelemetry-native.** Read the active span for trace/log correlation, or export logs as OTLP/HTTP records. The OTLP transport builds the JSON itself, so no `@opentelemetry/*` SDK is required to emit logs.
|
|
37
|
+
- **Runs outside Node too.** `logixia/browser` has no Node built-ins and is written for browsers and edge runtimes such as Cloudflare Workers.
|
|
38
|
+
- **Small footprint.** Three runtime dependencies (`commander`, `fast-json-stringify`, `picocolors`), no install scripts, ESM and CommonJS builds with types.
|
|
219
39
|
|
|
220
|
-
|
|
221
|
-
- **logixia is 1.2–1.9× faster than winston in every scenario**, with p99 latency of 1–5µs, against winston's 2.5–7.5µs.
|
|
222
|
-
- logixia is ahead on error logging, partly because pino also serializes the full stack through its error serializer.
|
|
223
|
-
- logixia's default human-readable console format is faster than its JSON mode. `npm run benchmark` prints both.
|
|
224
|
-
- The point of logixia is everything else in one package: non-blocking file and database transports, tracing, redaction, OpenTelemetry, NestJS and the CLI. It aims to be fast enough that you never notice it, without six extra installs.
|
|
40
|
+
Log calls are `async` and transport failures are caught internally, so a full disk or a flaky database does not crash your process.
|
|
225
41
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
| Operation | ops/sec | p99 |
|
|
229
|
-
| ---------------------------------------- | --------: | ----: |
|
|
230
|
-
| Wide event (accumulate 6 fields + emit) | 742,000 | 3.7µs |
|
|
231
|
-
| `safeStringify` (BigInt + circular) | 2,735,000 | 0.5µs |
|
|
232
|
-
| `decycle` + `retrocycle` round-trip | 1,003,000 | 1.3µs |
|
|
233
|
-
| Adaptive-sampling decision (hot path) | 1,950,000 | 0.9µs |
|
|
234
|
-
| Namespace child logging (`db.*` → debug) | 1,966,000 | 0.8µs |
|
|
235
|
-
|
|
236
|
-
Sampling and namespace resolution add **negligible overhead** (~µs), so you can keep them on in production.
|
|
237
|
-
|
|
238
|
-
To reproduce: `npm run benchmark` (core) and `npm run benchmark:features` (distinctive APIs).
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
## Installation
|
|
42
|
+
## Install
|
|
243
43
|
|
|
244
44
|
```bash
|
|
245
45
|
npm install logixia
|
|
246
|
-
pnpm add logixia
|
|
247
|
-
yarn add logixia
|
|
248
|
-
bun add logixia
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
For database transports, install the relevant driver alongside logixia:
|
|
252
|
-
|
|
253
|
-
```bash
|
|
254
|
-
npm install pg # PostgreSQL
|
|
255
|
-
npm install mysql2 # MySQL
|
|
256
|
-
npm install mongodb # MongoDB
|
|
257
|
-
npm install sqlite3 # SQLite
|
|
46
|
+
# or: pnpm add logixia | yarn add logixia | bun add logixia
|
|
258
47
|
```
|
|
259
48
|
|
|
260
|
-
**
|
|
49
|
+
**Node.js:** the library declares `node >=16` and is tested in CI on Node 18, 20 and 22. TypeScript 5.0+ is recommended for the bundled types. Building logixia from source needs a newer toolchain (Node `^22.19 || ^24.11 || >=26`); see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
261
50
|
|
|
262
|
-
|
|
51
|
+
Database transports use optional drivers that you install yourself: `pg`, `mysql2`, `mongodb`, or `sqlite3` plus `sqlite`.
|
|
263
52
|
|
|
264
53
|
## Quick start
|
|
265
54
|
|
|
266
55
|
```typescript
|
|
267
|
-
import { createLogger } from 'logixia';
|
|
56
|
+
import { createLogger, runWithTraceId } from 'logixia';
|
|
268
57
|
|
|
269
58
|
const logger = createLogger({
|
|
270
59
|
appName: 'api',
|
|
271
|
-
environment: '
|
|
60
|
+
environment: 'development',
|
|
61
|
+
traceId: true,
|
|
62
|
+
redact: { paths: ['password'] },
|
|
272
63
|
});
|
|
273
64
|
|
|
274
|
-
// ✅ Structured data — machine-readable, searchable, alertable
|
|
275
65
|
await logger.info('Server started', { port: 3000 });
|
|
276
|
-
await logger.warn('High memory usage', { used: '87%', threshold: '80%' });
|
|
277
|
-
await logger.error('Request failed', { orderId: 'ord_123', retryable: true });
|
|
278
|
-
|
|
279
|
-
// ✅ Pass an Error object directly — logixia serializes the full cause chain
|
|
280
|
-
await logger.error(new Error('Connection timeout'));
|
|
281
|
-
|
|
282
|
-
// ❌ Avoid string interpolation — you lose structured fields
|
|
283
|
-
// await logger.info(`Server started on port ${port}`);
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
No `try/catch` needed — logixia swallows transport errors internally so a flaky DB or disk-full condition never crashes your app.
|
|
287
66
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
```typescript
|
|
293
|
-
import { logger } from 'logixia';
|
|
294
|
-
|
|
295
|
-
await logger.info('Ready');
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
---
|
|
299
|
-
|
|
300
|
-
## Core concepts
|
|
301
|
-
|
|
302
|
-
### Log levels
|
|
303
|
-
|
|
304
|
-
logixia ships with six built-in levels in priority order: `error`, `warn`, `info`, `debug`, `trace`, `verbose`. Logs at or above the configured minimum level are emitted; the rest are dropped.
|
|
67
|
+
await runWithTraceId('req-8f3a', async () => {
|
|
68
|
+
await logger.warn('Slow query', { table: 'orders', ms: 842 });
|
|
69
|
+
await logger.info('Login', { user: 'alice', password: 'hunter2' });
|
|
70
|
+
});
|
|
305
71
|
|
|
306
|
-
|
|
307
|
-
await logger.error('Something went wrong');
|
|
308
|
-
await logger.warn('Approaching rate limit', { remaining: 5 });
|
|
309
|
-
await logger.info('Order created', { orderId: 'ord_123' });
|
|
310
|
-
await logger.debug('Cache miss', { key: 'user:456' });
|
|
311
|
-
await logger.trace('Entering function', { fn: 'processPayment' });
|
|
312
|
-
await logger.verbose('Full request payload', { body: req.body });
|
|
72
|
+
await logger.error(new Error('Connection timeout'), { orderId: 'ord_123' });
|
|
313
73
|
```
|
|
314
74
|
|
|
315
|
-
|
|
75
|
+
Output (colors omitted, stack trace shortened):
|
|
316
76
|
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
// AggregateError is handled too:
|
|
324
|
-
const err = new AggregateError([new Error('A'), new Error('B')], 'Multiple failures');
|
|
325
|
-
await logger.error(err);
|
|
77
|
+
```text
|
|
78
|
+
[2026-10-30T14:24:30.621Z] [INFO] [api] [ec339fb5-5afb-4675-8c39-ecbad4353818] Server started {"port":3000}
|
|
79
|
+
[2026-10-30T14:24:30.623Z] [WARN] [api] [req-8f3a] Slow query {"table":"orders","ms":842}
|
|
80
|
+
[2026-10-30T14:24:30.623Z] [INFO] [api] [req-8f3a] Login {"user":"alice","password":"[REDACTED]"}
|
|
81
|
+
[2026-10-30T14:24:30.625Z] [ERROR] [api] [ec339fb5-5afb-4675-8c39-ecbad4353818] Connection timeout {"orderId":"ord_123","error":{"name":"Error","message":"Connection timeout","stack":"Error: Connection timeout\n at main (...)"}}
|
|
326
82
|
```
|
|
327
83
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
```typescript
|
|
331
|
-
const logger = createLogger({
|
|
332
|
-
appName: 'payments',
|
|
333
|
-
levelOptions: {
|
|
334
|
-
level: 'payment', // show everything down to 'payment'
|
|
335
|
-
levels: {
|
|
336
|
-
error: 0,
|
|
337
|
-
warn: 1,
|
|
338
|
-
info: 2,
|
|
339
|
-
debug: 3,
|
|
340
|
-
// domain-specific levels — lower number = higher priority
|
|
341
|
-
payment: 1, // same priority as warn
|
|
342
|
-
audit: 2, // same priority as info
|
|
343
|
-
kafka: 2,
|
|
344
|
-
},
|
|
345
|
-
colors: {
|
|
346
|
-
error: 'red',
|
|
347
|
-
warn: 'yellow',
|
|
348
|
-
info: 'blue',
|
|
349
|
-
debug: 'green',
|
|
350
|
-
// IntelliSense now suggests 'payment' | 'audit' | 'kafka' as valid keys:
|
|
351
|
-
payment: 'brightYellow',
|
|
352
|
-
audit: 'magenta',
|
|
353
|
-
// kafka omitted → auto-palette assigns a visible color (cyan in this case)
|
|
354
|
-
},
|
|
355
|
-
},
|
|
356
|
-
});
|
|
84
|
+
Without a `transports` key logs go to the console. Add `transports` to write to files, databases or cloud services, all concurrently. There is also a ready-made default instance: `import { logger } from 'logixia'`.
|
|
357
85
|
|
|
358
|
-
|
|
359
|
-
await logger.payment('Charge captured', { orderId: 'ord_123', amount: 99.99 });
|
|
360
|
-
await logger.audit('Refund approved', { orderId: 'ord_123', by: 'admin' });
|
|
361
|
-
await logger.kafka('Message published', { topic: 'order.created' });
|
|
362
|
-
|
|
363
|
-
// logLevel() is the typed escape hatch — equivalent to the proxy methods above
|
|
364
|
-
await logger.logLevel('payment', 'Subscription renewed', { userId: 'usr_456' });
|
|
365
|
-
```
|
|
86
|
+
→ [Getting started](https://logixia.github.io/logixia/guide/getting-started) · [Configuration](https://logixia.github.io/logixia/guide/configuration) · [Log levels](https://logixia.github.io/logixia/guide/log-levels)
|
|
366
87
|
|
|
367
|
-
|
|
88
|
+
## Feature tour
|
|
368
89
|
|
|
369
|
-
|
|
90
|
+
### Request tracing
|
|
370
91
|
|
|
371
92
|
```typescript
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
levels: { error: 0, warn: 1, info: 2, kafka: 3, payment: 4 },
|
|
375
|
-
colors: { error: 'red', kafka: 'magenta' }, // ← IDE suggests 'kafka' | 'payment' here
|
|
376
|
-
},
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
svc.kafka('Producer connected'); // ✅ fully typed, no 'as any'
|
|
380
|
-
svc.payment('Charge captured', { txnId }); // ✅
|
|
381
|
-
```
|
|
93
|
+
import { createExpressContextMiddleware, createLogger, getCurrentTraceId } from 'logixia';
|
|
94
|
+
import { createExpressMiddleware } from 'logixia/middleware';
|
|
382
95
|
|
|
383
|
-
|
|
96
|
+
const logger = createLogger({ appName: 'api', traceId: true });
|
|
384
97
|
|
|
385
|
-
|
|
98
|
+
app.use(createExpressContextMiddleware({ responseHeader: 'X-Trace-Id' }));
|
|
99
|
+
app.use(createExpressMiddleware(logger)); // one request/response log line per call
|
|
386
100
|
|
|
387
|
-
|
|
388
|
-
await logger.info('
|
|
389
|
-
|
|
390
|
-
method: 'oauth',
|
|
391
|
-
provider: 'google',
|
|
392
|
-
durationMs: 42,
|
|
393
|
-
ip: '203.0.113.4',
|
|
101
|
+
app.get('/orders', async (_req, res) => {
|
|
102
|
+
await logger.info('Listing orders', { traceId: getCurrentTraceId() });
|
|
103
|
+
res.json([]);
|
|
394
104
|
});
|
|
395
105
|
```
|
|
396
106
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
```
|
|
400
|
-
[2025-03-14T10:22:01.412Z] [INFO] [api] [abc123def456] User authenticated {"userId":"usr_123","method":"oauth",...}
|
|
401
|
-
```
|
|
107
|
+
The trace ID is the first present header of `traceparent` (parsed to its 32-hex trace id), `x-trace-id`, `x-request-id`, `x-correlation-id` or `trace-id`, otherwise a generated UUID. Fastify has `createFastifyContextHook()` and `createFastifyPlugin()`; for anything else, wrap the handler in `runWithTraceId()`. `logixia/correlation` adds `X-Correlation-ID` propagation through `fetch`, axios, Kafka and SQS.
|
|
402
108
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
```json
|
|
406
|
-
{
|
|
407
|
-
"timestamp": "2025-03-14T10:22:01.412Z",
|
|
408
|
-
"level": "info",
|
|
409
|
-
"appName": "api",
|
|
410
|
-
"environment": "production",
|
|
411
|
-
"message": "User authenticated",
|
|
412
|
-
"traceId": "abc123def456",
|
|
413
|
-
"payload": { "userId": "usr_123", "method": "oauth", "provider": "google", "durationMs": 42 }
|
|
414
|
-
}
|
|
415
|
-
```
|
|
109
|
+
→ [Tracing](https://logixia.github.io/logixia/guide/tracing) · [HTTP logging](https://logixia.github.io/logixia/guide/http-logging) · [Child loggers and context](https://logixia.github.io/logixia/guide/child-loggers-and-context)
|
|
416
110
|
|
|
417
|
-
###
|
|
111
|
+
### NestJS
|
|
418
112
|
|
|
419
|
-
|
|
113
|
+
Everything NestJS-specific lives in `logixia/nest`, so plain Node apps never load `@nestjs/common`.
|
|
420
114
|
|
|
421
115
|
```typescript
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
userId: req.user.id,
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
await reqLogger.info('Processing order'); // includes requestId + userId in every entry
|
|
428
|
-
await reqLogger.info('Payment confirmed'); // same context, no repetition
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
### Adaptive log level
|
|
432
|
-
|
|
433
|
-
logixia automatically selects a sensible default level when no explicit level is configured:
|
|
434
|
-
|
|
435
|
-
| Condition | Default level |
|
|
436
|
-
| ---------------------- | :-----------: |
|
|
437
|
-
| `NODE_ENV=development` | `debug` |
|
|
438
|
-
| `NODE_ENV=test` | `warn` |
|
|
439
|
-
| `NODE_ENV=production` | `info` |
|
|
440
|
-
| `CI=true` | `info` |
|
|
441
|
-
| None of the above | `info` |
|
|
442
|
-
|
|
443
|
-
You can override this at any time via the `LOGIXIA_LEVEL` environment variable:
|
|
116
|
+
import { Module } from '@nestjs/common';
|
|
117
|
+
import { LogixiaLoggerModule } from 'logixia/nest';
|
|
444
118
|
|
|
445
|
-
|
|
446
|
-
|
|
119
|
+
@Module({
|
|
120
|
+
imports: [
|
|
121
|
+
LogixiaLoggerModule.forRoot({
|
|
122
|
+
appName: 'nestjs-api',
|
|
123
|
+
traceId: true,
|
|
124
|
+
transports: { console: {}, file: { filename: 'app.log', dirname: './logs' } },
|
|
125
|
+
}),
|
|
126
|
+
],
|
|
127
|
+
})
|
|
128
|
+
export class AppModule {}
|
|
447
129
|
```
|
|
448
130
|
|
|
449
|
-
|
|
131
|
+
The module registers the trace middleware, provides `LogixiaLoggerService` for injection and also ships `forRootAsync`, `@LogMethod()`, `LogixiaExceptionFilter`, and Kafka and WebSocket trace interceptors. A full app with Docker Compose is in [`examples/nestjs-app`](./examples/nestjs-app/).
|
|
450
132
|
|
|
451
|
-
|
|
452
|
-
logger.setLevel('debug');
|
|
453
|
-
console.log(logger.getLevel()); // 'debug'
|
|
454
|
-
```
|
|
133
|
+
→ [NestJS guide](https://logixia.github.io/logixia/guide/nestjs)
|
|
455
134
|
|
|
456
|
-
###
|
|
457
|
-
|
|
458
|
-
Child loggers use their context string as a **namespace**. You can pin different log levels to different namespaces in config, or override them with environment variables at runtime — without redeploying:
|
|
135
|
+
### Redaction
|
|
459
136
|
|
|
460
137
|
```typescript
|
|
461
138
|
const logger = createLogger({
|
|
462
139
|
appName: 'api',
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
'http.*': 'warn', // only warn+ from HTTP layer
|
|
468
|
-
payment: 'trace', // full trace for payment namespace
|
|
140
|
+
redact: {
|
|
141
|
+
paths: ['password', '*.token', 'req.headers.authorization', '**.creditCard'],
|
|
142
|
+
patterns: [/Bearer\s+\S+/gi],
|
|
143
|
+
censor: '[REDACTED]',
|
|
469
144
|
},
|
|
470
145
|
});
|
|
471
146
|
|
|
472
|
-
|
|
473
|
-
|
|
147
|
+
await logger.info('Login', { user: 'alice', password: 'hunter2' });
|
|
148
|
+
// → {"user":"alice","password":"[REDACTED]"}
|
|
474
149
|
```
|
|
475
150
|
|
|
476
|
-
|
|
151
|
+
Paths support `*` (one segment) and `**` (any depth), match case-insensitively, and the input object is never mutated. Circular references are printed as `[Circular]`.
|
|
477
152
|
|
|
478
|
-
|
|
479
|
-
# Override just the db namespace to trace, without changing anything else:
|
|
480
|
-
LOGIXIA_LEVEL_DB=trace node server.js
|
|
481
|
-
|
|
482
|
-
# Override the payment namespace:
|
|
483
|
-
LOGIXIA_LEVEL_PAYMENT=info node server.js
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
---
|
|
487
|
-
|
|
488
|
-
## Transports
|
|
153
|
+
→ [Redaction](https://logixia.github.io/logixia/guide/redaction)
|
|
489
154
|
|
|
490
|
-
|
|
155
|
+
### Transports
|
|
491
156
|
|
|
492
|
-
|
|
157
|
+
All transports run concurrently. Built-in ones are configured under `transports`; the rest are classes you pass in `transports.custom`.
|
|
493
158
|
|
|
494
159
|
```typescript
|
|
495
|
-
|
|
496
|
-
appName: 'api',
|
|
497
|
-
environment: 'development',
|
|
498
|
-
format: {
|
|
499
|
-
colorize: true, // ANSI colour output
|
|
500
|
-
timestamp: true, // include ISO timestamp
|
|
501
|
-
json: false, // text format; set to true for JSON
|
|
502
|
-
},
|
|
503
|
-
transports: {
|
|
504
|
-
console: {
|
|
505
|
-
level: 'debug', // minimum level for this transport only
|
|
506
|
-
},
|
|
507
|
-
},
|
|
508
|
-
});
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### File with rotation
|
|
160
|
+
import { createLogger, OtlpLogTransport, SlackTransport } from 'logixia';
|
|
512
161
|
|
|
513
|
-
No extra packages. Rotation by size or time interval, automatic gzip compression, configurable retention — all built-in:
|
|
514
|
-
|
|
515
|
-
```typescript
|
|
516
162
|
const logger = createLogger({
|
|
517
163
|
appName: 'api',
|
|
518
|
-
environment: 'production',
|
|
519
164
|
transports: {
|
|
165
|
+
console: { level: 'info' },
|
|
520
166
|
file: {
|
|
521
167
|
filename: 'app.log',
|
|
522
168
|
dirname: './logs',
|
|
523
|
-
maxSize: '50MB',
|
|
524
|
-
maxFiles: 14,
|
|
525
|
-
zippedArchive: true,
|
|
526
|
-
datePattern: 'YYYY-MM-DD', // optional: also rotate when the day changes
|
|
527
|
-
format: 'json', // 'json' | 'text' | 'csv'
|
|
528
|
-
batchSize: 100, // buffer up to 100 entries before writing
|
|
529
|
-
flushInterval: 2000, // flush buffer every 2 seconds
|
|
169
|
+
maxSize: '50MB',
|
|
170
|
+
maxFiles: 14,
|
|
171
|
+
zippedArchive: true,
|
|
530
172
|
},
|
|
173
|
+
custom: [
|
|
174
|
+
new OtlpLogTransport({ url: 'http://localhost:4318/v1/logs', serviceName: 'api' }),
|
|
175
|
+
new SlackTransport({ webhookUrl: process.env.SLACK_WEBHOOK_URL!, level: 'error' }),
|
|
176
|
+
],
|
|
531
177
|
},
|
|
532
178
|
});
|
|
533
179
|
```
|
|
534
180
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
181
|
+
| Transport | Where it comes from | Use it for |
|
|
182
|
+
| ----------------------------------------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------- |
|
|
183
|
+
| [Console](https://logixia.github.io/logixia/guide/transports/console) | `transports.console` | Colorized dev output or JSON to stdout |
|
|
184
|
+
| [File](https://logixia.github.io/logixia/guide/transports/file) | `transports.file` | Size/date rotation, gzip, retention |
|
|
185
|
+
| [Database](https://logixia.github.io/logixia/guide/transports/database) | `transports.database` | PostgreSQL, MySQL, MongoDB, SQLite |
|
|
186
|
+
| [CloudWatch](https://logixia.github.io/logixia/guide/transports/cloudwatch) | `logixia/cloudwatch` | AWS CloudWatch Logs, optional EMF metrics |
|
|
187
|
+
| [Google Cloud](https://logixia.github.io/logixia/guide/transports/gcp) | `logixia/gcp` | Cloud Logging with Cloud Trace correlation |
|
|
188
|
+
| [Azure Monitor](https://logixia.github.io/logixia/guide/transports/azure) | `logixia/azure` | Logs Ingestion API (Data Collection Rule) |
|
|
189
|
+
| [Datadog](https://logixia.github.io/logixia/guide/transports/datadog) | `transports.analytics.datadog`, `logixia/transports` | Logs and metrics to Datadog |
|
|
190
|
+
| [OTLP](https://logixia.github.io/logixia/guide/transports/otlp) | `logixia`, `logixia/transports` | OTLP/HTTP to Grafana, Axiom, SigNoz, a Collector |
|
|
191
|
+
| [Analytics](https://logixia.github.io/logixia/guide/transports/analytics) | `transports.analytics` | Mixpanel, Segment, Google Analytics, Datadog |
|
|
192
|
+
| [Slack / Discord](https://logixia.github.io/logixia/guide/transports/slack-discord) | `logixia`, `logixia/transports` | Alerting to a chat channel via webhooks |
|
|
193
|
+
| [Worker thread](https://logixia.github.io/logixia/guide/transports/worker) | `logixia/worker` | Move file/database/console I/O off the main thread |
|
|
194
|
+
| [Custom](https://logixia.github.io/logixia/guide/transports/custom) | `transports.custom` with `ITransport` | Anything else: S3, queues, internal services |
|
|
546
195
|
|
|
547
|
-
|
|
196
|
+
→ [All transports](https://logixia.github.io/logixia/guide/transports/)
|
|
548
197
|
|
|
549
|
-
|
|
198
|
+
### OpenTelemetry
|
|
550
199
|
|
|
551
200
|
```typescript
|
|
552
|
-
|
|
553
|
-
file: {
|
|
554
|
-
filename: 'app.log',
|
|
555
|
-
dirname: './logs',
|
|
556
|
-
rotation: {
|
|
557
|
-
interval: '1d', // rotate daily: '1h' | '6h' | '12h' | '1d' | '1w'
|
|
558
|
-
maxSize: '100MB', // and on size
|
|
559
|
-
maxFiles: 30,
|
|
560
|
-
compress: true,
|
|
561
|
-
// or decide yourself, checked before each write:
|
|
562
|
-
// shouldRotate: (file, { size, created }) => size > 100 * 1024 * 1024,
|
|
563
|
-
},
|
|
564
|
-
},
|
|
565
|
-
},
|
|
566
|
-
```
|
|
201
|
+
import { createLogger, initOtelBridge } from 'logixia';
|
|
567
202
|
|
|
568
|
-
|
|
203
|
+
initOtelBridge(); // call once, after your OTel SDK starts
|
|
569
204
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
transports: {
|
|
574
|
-
file: [
|
|
575
|
-
{ filename: 'app.log', dirname: './logs', format: 'json' },
|
|
576
|
-
{ filename: 'error.log', dirname: './logs', format: 'json', level: 'error' },
|
|
577
|
-
],
|
|
578
|
-
},
|
|
205
|
+
const logger = createLogger({ appName: 'checkout' });
|
|
206
|
+
await logger.info('Payment processed', { orderId: 'ord_123' });
|
|
207
|
+
// the active span's traceId and spanId are added to the entry
|
|
579
208
|
```
|
|
580
209
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
Write structured logs directly to your database — batched, non-blocking, with configurable flush intervals:
|
|
584
|
-
|
|
585
|
-
```typescript
|
|
586
|
-
// PostgreSQL
|
|
587
|
-
const logger = createLogger({
|
|
588
|
-
appName: 'api',
|
|
589
|
-
environment: 'production',
|
|
590
|
-
transports: {
|
|
591
|
-
database: {
|
|
592
|
-
type: 'postgresql',
|
|
593
|
-
host: 'localhost',
|
|
594
|
-
port: 5432,
|
|
595
|
-
database: 'appdb',
|
|
596
|
-
table: 'logs',
|
|
597
|
-
username: 'dbuser',
|
|
598
|
-
password: process.env.DB_PASSWORD,
|
|
599
|
-
batchSize: 100, // write in batches of 100
|
|
600
|
-
flushInterval: 5000, // flush every 5 seconds
|
|
601
|
-
},
|
|
602
|
-
},
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
// MongoDB
|
|
606
|
-
const logger = createLogger({
|
|
607
|
-
appName: 'api',
|
|
608
|
-
environment: 'production',
|
|
609
|
-
transports: {
|
|
610
|
-
database: {
|
|
611
|
-
type: 'mongodb',
|
|
612
|
-
connectionString: process.env.MONGO_URI,
|
|
613
|
-
database: 'appdb',
|
|
614
|
-
collection: 'logs',
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
// MySQL
|
|
620
|
-
const logger = createLogger({
|
|
621
|
-
appName: 'api',
|
|
622
|
-
environment: 'production',
|
|
623
|
-
transports: {
|
|
624
|
-
database: {
|
|
625
|
-
type: 'mysql',
|
|
626
|
-
host: 'localhost',
|
|
627
|
-
database: 'appdb',
|
|
628
|
-
table: 'logs',
|
|
629
|
-
username: 'root',
|
|
630
|
-
password: process.env.MYSQL_PASSWORD,
|
|
631
|
-
},
|
|
632
|
-
},
|
|
633
|
-
});
|
|
210
|
+
`initOtelBridge()` uses `@opentelemetry/api` when it is installed and does nothing when it is not. To export logs rather than only correlate them, add `OtlpLogTransport` (see Transports above). Ready-made OTLP configs for Grafana, Axiom, Better Stack and a local Collector are in the [OTLP transport guide](https://logixia.github.io/logixia/guide/transports/otlp).
|
|
634
211
|
|
|
635
|
-
|
|
636
|
-
const logger = createLogger({
|
|
637
|
-
appName: 'api',
|
|
638
|
-
environment: 'development',
|
|
639
|
-
transports: {
|
|
640
|
-
database: {
|
|
641
|
-
type: 'sqlite',
|
|
642
|
-
database: './logs/app.sqlite',
|
|
643
|
-
table: 'logs',
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
});
|
|
647
|
-
```
|
|
212
|
+
→ [OpenTelemetry](https://logixia.github.io/logixia/guide/opentelemetry) · [OTLP transport](https://logixia.github.io/logixia/guide/transports/otlp)
|
|
648
213
|
|
|
649
|
-
|
|
214
|
+
### Browser and edge
|
|
650
215
|
|
|
651
216
|
```typescript
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
],
|
|
657
|
-
},
|
|
217
|
+
import { createBrowserLogger } from 'logixia/browser';
|
|
218
|
+
|
|
219
|
+
const logger = createBrowserLogger({ appName: 'web', level: 'info' });
|
|
220
|
+
logger.info('Page loaded', { route: '/home' });
|
|
658
221
|
```
|
|
659
222
|
|
|
660
|
-
|
|
223
|
+
`logixia/browser` has no Node built-ins and ships a console transport plus a batching `BrowserRemoteTransport` that uses `fetch`. Working examples: [`examples/cloudflare-worker`](./examples/cloudflare-worker/) and [`examples/nextjs`](./examples/nextjs/).
|
|
661
224
|
|
|
662
|
-
|
|
225
|
+
→ [Browser and edge](https://logixia.github.io/logixia/guide/browser-and-edge)
|
|
663
226
|
|
|
664
|
-
|
|
227
|
+
### Sampling and runtime levels
|
|
665
228
|
|
|
666
229
|
```typescript
|
|
667
|
-
import { DataDogTransport } from 'logixia/transports';
|
|
668
|
-
|
|
669
230
|
const logger = createLogger({
|
|
670
231
|
appName: 'api',
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
site: 'datadoghq.com', // or 'datadoghq.eu', 'us3.datadoghq.com', 'us5.datadoghq.com', 'ap1.datadoghq.com', 'ddog-gov.com'
|
|
677
|
-
service: 'api',
|
|
678
|
-
env: 'production',
|
|
679
|
-
enableLogs: true,
|
|
680
|
-
enableMetrics: true,
|
|
681
|
-
enableTraces: true,
|
|
682
|
-
},
|
|
683
|
-
},
|
|
232
|
+
namespaceLevels: { 'db.*': 'debug', '*': 'info' },
|
|
233
|
+
sampling: {
|
|
234
|
+
rate: 0.1, // keep 10% in steady state
|
|
235
|
+
traceConsistent: true, // keep or drop a whole trace together
|
|
236
|
+
adaptive: { errorRateThreshold: 0.05, boostRate: 1.0, windowMs: 10_000 },
|
|
684
237
|
},
|
|
685
238
|
});
|
|
686
239
|
```
|
|
687
240
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
```typescript
|
|
691
|
-
transports: {
|
|
692
|
-
analytics: {
|
|
693
|
-
mixpanel: {
|
|
694
|
-
token: process.env.MIXPANEL_TOKEN!,
|
|
695
|
-
enableSuperProperties: true,
|
|
696
|
-
superProperties: { platform: 'web', version: '2.0' },
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
},
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
**Segment:**
|
|
241
|
+
When the windowed error rate crosses the threshold, the sample rate is raised until things calm down. You can also change levels in a running process: `registerLevelSignal(logger)` cycles the level on `SIGUSR2`, and `createLevelControlHandler(logger)` gives you an HTTP handler to mount behind your own auth.
|
|
703
242
|
|
|
704
|
-
|
|
705
|
-
transports: {
|
|
706
|
-
analytics: {
|
|
707
|
-
segment: {
|
|
708
|
-
writeKey: process.env.SEGMENT_WRITE_KEY!,
|
|
709
|
-
enableBatching: true,
|
|
710
|
-
flushAt: 20,
|
|
711
|
-
flushInterval: 10_000,
|
|
712
|
-
},
|
|
713
|
-
},
|
|
714
|
-
},
|
|
715
|
-
```
|
|
243
|
+
→ [Sampling](https://logixia.github.io/logixia/guide/sampling) · [Log levels](https://logixia.github.io/logixia/guide/log-levels) · [Graceful shutdown](https://logixia.github.io/logixia/guide/graceful-shutdown)
|
|
716
244
|
|
|
717
|
-
|
|
245
|
+
### Metrics
|
|
718
246
|
|
|
719
247
|
```typescript
|
|
720
|
-
|
|
721
|
-
analytics: {
|
|
722
|
-
googleAnalytics: {
|
|
723
|
-
measurementId: process.env.GA_MEASUREMENT_ID!,
|
|
724
|
-
apiSecret: process.env.GA_API_SECRET!,
|
|
725
|
-
enableEcommerce: false,
|
|
726
|
-
},
|
|
727
|
-
},
|
|
728
|
-
},
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
### Multiple transports simultaneously
|
|
732
|
-
|
|
733
|
-
All configured transports receive every log entry concurrently — no sequential bottleneck:
|
|
248
|
+
import { createLogger, createMetricsPlugin } from 'logixia';
|
|
734
249
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
appName: 'api',
|
|
738
|
-
environment: 'production',
|
|
739
|
-
transports: {
|
|
740
|
-
console: { format: 'json' },
|
|
741
|
-
file: { filename: 'app.log', dirname: './logs', maxSize: '100MB' },
|
|
742
|
-
database: {
|
|
743
|
-
type: 'postgresql',
|
|
744
|
-
host: 'localhost',
|
|
745
|
-
database: 'appdb',
|
|
746
|
-
table: 'logs',
|
|
747
|
-
},
|
|
748
|
-
analytics: {
|
|
749
|
-
datadog: {
|
|
750
|
-
apiKey: process.env.DD_API_KEY!,
|
|
751
|
-
service: 'api',
|
|
752
|
-
},
|
|
753
|
-
},
|
|
754
|
-
},
|
|
250
|
+
const metrics = createMetricsPlugin({
|
|
251
|
+
http_requests_total: { type: 'counter', help: 'HTTP requests', labels: ['method', 'status'] },
|
|
755
252
|
});
|
|
253
|
+
const logger = createLogger({ appName: 'api' }).use(metrics);
|
|
756
254
|
|
|
757
|
-
|
|
758
|
-
|
|
255
|
+
await logger.info('request handled', { method: 'GET', status: 200 });
|
|
256
|
+
app.get('/metrics', metrics.expressHandler()); // Prometheus text format
|
|
759
257
|
```
|
|
760
258
|
|
|
761
|
-
|
|
259
|
+
→ [Metrics](https://logixia.github.io/logixia/guide/metrics)
|
|
762
260
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
```typescript
|
|
766
|
-
import { createLogger } from 'logixia';
|
|
767
|
-
import { DiscordTransport, SlackTransport } from 'logixia';
|
|
261
|
+
### CLI and log search
|
|
768
262
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
new SlackTransport({ webhookUrl: process.env.SLACK_WEBHOOK_URL!, level: 'error' }),
|
|
774
|
-
new DiscordTransport({ webhookUrl: process.env.DISCORD_WEBHOOK_URL!, level: 'warn' }),
|
|
775
|
-
],
|
|
776
|
-
},
|
|
777
|
-
});
|
|
263
|
+
```bash
|
|
264
|
+
npx logixia tail ./logs/app.log --follow --filter level:error
|
|
265
|
+
npx logixia search ./logs/app.log --query "payment failed"
|
|
266
|
+
npx logixia explore ./logs/app.log # interactive terminal explorer
|
|
778
267
|
```
|
|
779
268
|
|
|
780
|
-
|
|
781
|
-
- Logged text is escaped: Slack `&`, `<`, `>` (so `<!channel>` and `<@U123>` in log data cannot ping anyone); Discord markdown, plus `allowed_mentions: { parse: [] }` on every request.
|
|
782
|
-
- Platform limits are enforced by truncation (Slack header 150 and data 2,500 chars; Discord title 256, description 4,096, 25 fields, 6,000 per message).
|
|
783
|
-
- HTTP 429 waits for `Retry-After` (Discord: `retry_after`), then retries up to `maxRetries` (default 1). Other failures keep the batch queued with backoff; `flush()` rejects with a host-only error. The queue is bounded by `maxBufferSize` (default 1000, oldest dropped).
|
|
784
|
-
- Also available: `username`, `iconEmoji` (Slack), `avatarUrl` (Discord), `filter`, `formatter`, `flush()` and `close()` (flushes pending entries).
|
|
269
|
+
The CLI also has `stats`, `analyze`, `export` and `query`. For in-process search, `logixia/search` exports `SearchManager`, which indexes entries in memory and supports text, filters, natural-language queries and trace correlation.
|
|
785
270
|
|
|
786
|
-
|
|
271
|
+
→ [CLI](https://logixia.github.io/logixia/guide/cli) · [Log search](https://logixia.github.io/logixia/guide/log-search)
|
|
787
272
|
|
|
788
|
-
|
|
273
|
+
### More
|
|
789
274
|
|
|
790
|
-
|
|
791
|
-
import type { ITransport, TransportLogEntry } from 'logixia';
|
|
792
|
-
|
|
793
|
-
class SlackTransport implements ITransport {
|
|
794
|
-
name = 'slack';
|
|
795
|
-
|
|
796
|
-
async write(entry: TransportLogEntry): Promise<void> {
|
|
797
|
-
if (entry.level !== 'error' && entry.level !== 'fatal') return;
|
|
798
|
-
await fetch(process.env.SLACK_WEBHOOK_URL!, {
|
|
799
|
-
method: 'POST',
|
|
800
|
-
headers: { 'Content-Type': 'application/json' },
|
|
801
|
-
body: JSON.stringify({
|
|
802
|
-
text: `*[${entry.level.toUpperCase()}]* ${entry.message}`,
|
|
803
|
-
attachments: [{ text: JSON.stringify(entry.data, null, 2) }],
|
|
804
|
-
}),
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
async close(): Promise<void> {
|
|
809
|
-
// optional cleanup
|
|
810
|
-
}
|
|
811
|
-
}
|
|
275
|
+
Typed fields with `createTypedLogger` and `defineLogSchema` ([typed logger](https://logixia.github.io/logixia/guide/typed-logger)), wide events / canonical log lines with `wideEventMiddleware` and `addEventFields`, a plugin API with `onInit` / `onLog` / `onError` / `onShutdown` hooks, and `createMockLogger` from `logixia/testing` for unit tests ([testing](https://logixia.github.io/logixia/guide/testing)).
|
|
812
276
|
|
|
813
|
-
|
|
814
|
-
appName: 'api',
|
|
815
|
-
environment: 'production',
|
|
816
|
-
transports: {
|
|
817
|
-
custom: [new SlackTransport()],
|
|
818
|
-
},
|
|
819
|
-
});
|
|
820
|
-
```
|
|
277
|
+
## Upgrading from 1.x
|
|
821
278
|
|
|
822
|
-
|
|
279
|
+
2.0 has one breaking change: everything NestJS-specific moved from the root entry to `logixia/nest`.
|
|
823
280
|
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
level: string;
|
|
828
|
-
message: string;
|
|
829
|
-
data?: Record<string, unknown>;
|
|
830
|
-
context?: string;
|
|
831
|
-
traceId?: string;
|
|
832
|
-
appName?: string;
|
|
833
|
-
environment?: string;
|
|
834
|
-
}
|
|
281
|
+
```diff
|
|
282
|
+
- import { LogixiaLoggerModule, LogMethod, InjectLogger } from 'logixia';
|
|
283
|
+
+ import { LogixiaLoggerModule, LogMethod, InjectLogger } from 'logixia/nest';
|
|
835
284
|
```
|
|
836
285
|
|
|
837
|
-
|
|
286
|
+
The root `logixia` entry no longer loads `@nestjs/common`, so Express, Fastify and plain Node apps work without NestJS installed. → [Migration guide](https://logixia.github.io/logixia/guide/migration-v2)
|
|
838
287
|
|
|
839
|
-
##
|
|
288
|
+
## Security
|
|
840
289
|
|
|
841
|
-
|
|
290
|
+
- **Redaction** runs before any transport sees an entry, and HTTP logging redacts sensitive headers and query parameters by default.
|
|
291
|
+
- **Secrets stay out of output.** Transport credentials (API keys, passwords, webhook URLs, tokens) are hidden from `JSON.stringify`, `util.inspect` and error messages.
|
|
292
|
+
- **Supply chain.** Releases are published from CI with [npm provenance](https://docs.npmjs.com/generating-provenance-statements) through npm trusted publishing, so there is no long-lived npm token. The package has no install scripts and three runtime dependencies.
|
|
293
|
+
- **Reporting.** Please report vulnerabilities privately through [GitHub private vulnerability reporting](https://github.com/Logixia/logixia/security/advisories/new). See [SECURITY.md](./SECURITY.md).
|
|
842
294
|
|
|
843
|
-
|
|
295
|
+
→ [Security guide](https://logixia.github.io/logixia/guide/security)
|
|
844
296
|
|
|
845
|
-
|
|
297
|
+
## Documentation and examples
|
|
846
298
|
|
|
847
|
-
|
|
848
|
-
|
|
299
|
+
| Topic | Link |
|
|
300
|
+
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
301
|
+
| Full documentation | [logixia.github.io/logixia](https://logixia.github.io/logixia/) |
|
|
302
|
+
| Configuration reference | [guide/configuration](https://logixia.github.io/logixia/guide/configuration) |
|
|
303
|
+
| API reference | [guide/api](https://logixia.github.io/logixia/guide/api) |
|
|
304
|
+
| Runnable examples (Express, Fastify, Hono, NestJS, Next.js, Workers) | [`examples/`](./examples/) |
|
|
305
|
+
| Changelog | [CHANGELOG.md](./CHANGELOG.md) |
|
|
849
306
|
|
|
850
|
-
|
|
851
|
-
appName: 'api',
|
|
852
|
-
environment: 'production',
|
|
853
|
-
transports: {
|
|
854
|
-
custom: [
|
|
855
|
-
new CloudWatchTransport({
|
|
856
|
-
region: 'us-east-1', // or set AWS_REGION env var
|
|
857
|
-
logGroupName: '/app/api',
|
|
858
|
-
logStreamName: 'api-server-1', // defaults to hostname + PID
|
|
859
|
-
// Credentials: see "Credential sources" below — no hard-coding needed.
|
|
860
|
-
batchSize: 100, // default: 100
|
|
861
|
-
flushIntervalMs: 5000, // default: 5000
|
|
862
|
-
emf: true, // emit numeric fields as CloudWatch Metrics
|
|
863
|
-
level: 'warn', // forward only warn+ to CloudWatch
|
|
864
|
-
}),
|
|
865
|
-
],
|
|
866
|
-
},
|
|
867
|
-
});
|
|
868
|
-
```
|
|
307
|
+
## Contributing
|
|
869
308
|
|
|
870
|
-
|
|
309
|
+
Pull requests are welcome. For larger changes, open an issue first. Setup, scripts and the commit convention are in [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
871
310
|
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
|
|
311
|
+
```bash
|
|
312
|
+
git clone https://github.com/Logixia/logixia.git
|
|
313
|
+
cd logixia
|
|
314
|
+
npm ci --ignore-scripts
|
|
315
|
+
npm test
|
|
875
316
|
```
|
|
876
317
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
1. `accessKeyId` / `secretAccessKey` / `sessionToken` in the transport config
|
|
880
|
-
2. `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`
|
|
881
|
-
3. The shared credentials file: `~/.aws/credentials`, or `AWS_SHARED_CREDENTIALS_FILE`, using the `AWS_PROFILE` profile (default `default`)
|
|
882
|
-
4. ECS container credentials: `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` or `AWS_CONTAINER_CREDENTIALS_FULL_URI` (with `AWS_CONTAINER_AUTHORIZATION_TOKEN` or `_FILE`)
|
|
883
|
-
5. EC2 instance role through IMDSv2 (set `AWS_EC2_METADATA_DISABLED=true` to skip)
|
|
884
|
-
|
|
885
|
-
Not supported: SSO, assume-role profiles, `credential_process`, web identity (EKS IRSA). If no source yields credentials, events stay buffered (and the buffer cap applies) rather than being dropped, and a warning is logged.
|
|
886
|
-
|
|
887
|
-
The log group and stream are created on first use; an already-existing one is fine. The IAM role needs `logs:CreateLogGroup`, `logs:CreateLogStream` and `logs:PutLogEvents` (if the group and stream already exist, only `PutLogEvents` is required, and a failed `CreateLogGroup` is tolerated). Use `endpoint` to point at LocalStack or a VPC endpoint.
|
|
888
|
-
|
|
889
|
-
### Google Cloud Logging
|
|
890
|
-
|
|
891
|
-
Maps logixia levels to GCP `severity` values (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`), auto-injects `logging.googleapis.com/trace` for Cloud Trace correlation, and supports Application Default Credentials (ADC) — no service account JSON required when running on GKE / Cloud Run / App Engine.
|
|
892
|
-
|
|
893
|
-
```typescript
|
|
894
|
-
import { GCPTransport } from 'logixia/gcp';
|
|
895
|
-
|
|
896
|
-
const logger = createLogger({
|
|
897
|
-
appName: 'api',
|
|
898
|
-
environment: 'production',
|
|
899
|
-
transports: {
|
|
900
|
-
custom: [
|
|
901
|
-
new GCPTransport({
|
|
902
|
-
projectId: 'my-gcp-project', // or set GOOGLE_CLOUD_PROJECT env var
|
|
903
|
-
logName: 'projects/my-gcp-project/logs/api',
|
|
904
|
-
resource: { type: 'k8s_container', labels: { cluster_name: 'prod' } },
|
|
905
|
-
// credentials: { client_email: '...', private_key: '...' }
|
|
906
|
-
// Omit to use ADC (recommended on GCP-hosted infrastructure)
|
|
907
|
-
batchSize: 200,
|
|
908
|
-
flushIntervalMs: 5000,
|
|
909
|
-
}),
|
|
910
|
-
],
|
|
911
|
-
},
|
|
912
|
-
});
|
|
913
|
-
```
|
|
318
|
+
## Sponsor
|
|
914
319
|
|
|
915
|
-
|
|
320
|
+
logixia is free and MIT-licensed, and it is built and maintained by one person. If it saves you time, sponsoring keeps it moving:
|
|
916
321
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
```typescript
|
|
920
|
-
import { AzureMonitorTransport } from 'logixia/azure';
|
|
921
|
-
|
|
922
|
-
const logger = createLogger({
|
|
923
|
-
appName: 'api',
|
|
924
|
-
environment: 'production',
|
|
925
|
-
transports: {
|
|
926
|
-
custom: [
|
|
927
|
-
new AzureMonitorTransport({
|
|
928
|
-
endpoint: 'https://<dce-name>.ingest.monitor.azure.com',
|
|
929
|
-
ruleId: 'dcr-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
|
930
|
-
streamName: 'Custom-LogixiaLogs_CL',
|
|
931
|
-
tenantId: process.env.AZURE_TENANT_ID,
|
|
932
|
-
clientId: process.env.AZURE_CLIENT_ID,
|
|
933
|
-
clientSecret: process.env.AZURE_CLIENT_SECRET,
|
|
934
|
-
batchSize: 200,
|
|
935
|
-
flushIntervalMs: 5000,
|
|
936
|
-
}),
|
|
937
|
-
],
|
|
938
|
-
},
|
|
939
|
-
});
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
All three cloud transports expose `flush()` and `close()` so they participate in logixia's [graceful shutdown](#graceful-shutdown) flow automatically.
|
|
943
|
-
|
|
944
|
-
---
|
|
945
|
-
|
|
946
|
-
## Request tracing
|
|
947
|
-
|
|
948
|
-
logixia uses `AsyncLocalStorage` to propagate trace IDs through your entire async call graph automatically — no passing of context objects, no manual threading.
|
|
949
|
-
|
|
950
|
-
### Core trace utilities
|
|
951
|
-
|
|
952
|
-
```typescript
|
|
953
|
-
import {
|
|
954
|
-
generateTraceId, // create a UUID v4 trace ID
|
|
955
|
-
getCurrentTraceId, // read trace ID from current async context
|
|
956
|
-
runWithTraceId, // run a callback inside a new trace context
|
|
957
|
-
setTraceId, // set trace ID in the CURRENT context (use sparingly)
|
|
958
|
-
extractTraceId, // extract a trace ID from a request-like object
|
|
959
|
-
} from 'logixia';
|
|
960
|
-
|
|
961
|
-
// Generate a new trace ID
|
|
962
|
-
const traceId = generateTraceId();
|
|
963
|
-
// → 'a3f1c2b4-...'
|
|
964
|
-
|
|
965
|
-
// Run code inside a trace context — every logger.* call within the callback
|
|
966
|
-
// (including across await boundaries and Promise.all) will carry this trace ID
|
|
967
|
-
runWithTraceId(traceId, async () => {
|
|
968
|
-
await logger.info('Processing job'); // traceId attached automatically
|
|
969
|
-
await processItems(); // all nested async calls carry it too
|
|
970
|
-
});
|
|
971
|
-
|
|
972
|
-
// Read the trace ID currently in context (returns undefined if none is set)
|
|
973
|
-
const current = getCurrentTraceId();
|
|
974
|
-
|
|
975
|
-
// Extract a trace ID from an incoming request object
|
|
976
|
-
const incomingTraceId = extractTraceId(req, {
|
|
977
|
-
header: ['traceparent', 'x-trace-id', 'x-request-id'],
|
|
978
|
-
query: ['traceId'],
|
|
979
|
-
});
|
|
980
|
-
```
|
|
981
|
-
|
|
982
|
-
### Express / Fastify middleware
|
|
983
|
-
|
|
984
|
-
```typescript
|
|
985
|
-
import { traceMiddleware } from 'logixia';
|
|
986
|
-
|
|
987
|
-
// Zero-config — reads from traceparent / x-trace-id / x-request-id / x-correlation-id
|
|
988
|
-
// and generates a UUID v4 if none is present. Sets X-Trace-Id on the response.
|
|
989
|
-
app.use(traceMiddleware());
|
|
990
|
-
|
|
991
|
-
// With custom config:
|
|
992
|
-
app.use(
|
|
993
|
-
traceMiddleware({
|
|
994
|
-
enabled: true,
|
|
995
|
-
generator: () => `req_${crypto.randomUUID()}`,
|
|
996
|
-
extractor: {
|
|
997
|
-
header: ['x-trace-id', 'traceparent'],
|
|
998
|
-
query: ['traceId'],
|
|
999
|
-
},
|
|
1000
|
-
})
|
|
1001
|
-
);
|
|
1002
|
-
|
|
1003
|
-
// Service layer — no parameters needed, trace ID propagates automatically
|
|
1004
|
-
class OrderService {
|
|
1005
|
-
async createOrder(data: OrderData) {
|
|
1006
|
-
await logger.info('Creating order', { items: data.items.length });
|
|
1007
|
-
// ^ trace ID is automatically included
|
|
1008
|
-
await this.processPayment(data);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
async processPayment(data: OrderData) {
|
|
1012
|
-
await logger.info('Processing payment', { amount: data.total });
|
|
1013
|
-
// ^ same trace ID, propagated automatically through await
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
```
|
|
1017
|
-
|
|
1018
|
-
The default headers checked for an incoming trace ID (in priority order) are: `traceparent`, `x-trace-id`, `x-request-id`, `x-correlation-id`, `trace-id`.
|
|
1019
|
-
|
|
1020
|
-
### Hono
|
|
1021
|
-
|
|
1022
|
-
On Node, Hono needs nothing beyond `runWithTraceId()` — wrap `await next()` and every log line in the request carries the trace ID:
|
|
1023
|
-
|
|
1024
|
-
```typescript
|
|
1025
|
-
app.use(async (c, next) => {
|
|
1026
|
-
const traceId =
|
|
1027
|
-
extractTraceId({ headers: c.req.header() }, { header: DEFAULT_TRACE_HEADERS }) ??
|
|
1028
|
-
generateTraceId();
|
|
1029
|
-
await runWithTraceId(traceId, next);
|
|
1030
|
-
});
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
See [`examples/hono-example.ts`](./examples/hono-example.ts) (`npm run dev:hono`) for the full version.
|
|
1034
|
-
|
|
1035
|
-
### Next.js (App Router)
|
|
1036
|
-
|
|
1037
|
-
In a route handler on the Node.js runtime (`export const runtime = 'nodejs'`) the regular `logixia` entry works: wrap the handler body in `runWithTraceId(traceId, ...)`. See [`examples/nextjs/`](./examples/nextjs/) for a copy-paste route handler.
|
|
1038
|
-
|
|
1039
|
-
### NestJS trace middleware
|
|
1040
|
-
|
|
1041
|
-
The `TraceMiddleware` class integrates directly with NestJS's middleware system. `LogixiaLoggerModule.forRoot()` applies it automatically across all routes — no manual wiring needed:
|
|
1042
|
-
|
|
1043
|
-
```typescript
|
|
1044
|
-
// Applied automatically by LogixiaLoggerModule.forRoot().
|
|
1045
|
-
// For manual use in a custom module:
|
|
1046
|
-
|
|
1047
|
-
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
|
1048
|
-
import { TraceMiddleware } from 'logixia/nest';
|
|
1049
|
-
|
|
1050
|
-
@Module({})
|
|
1051
|
-
export class AppModule implements NestModule {
|
|
1052
|
-
configure(consumer: MiddlewareConsumer) {
|
|
1053
|
-
consumer.apply(TraceMiddleware).forRoutes('*');
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
```
|
|
1057
|
-
|
|
1058
|
-
### Kafka trace interceptor
|
|
1059
|
-
|
|
1060
|
-
Propagates trace IDs through Kafka message handlers. Reads `traceId` / `trace_id` / `x-trace-id` from the message body or headers and runs the handler inside that trace context:
|
|
1061
|
-
|
|
1062
|
-
```typescript
|
|
1063
|
-
import { KafkaTraceInterceptor } from 'logixia/nest';
|
|
1064
|
-
import { UseInterceptors, Controller } from '@nestjs/common';
|
|
1065
|
-
import { MessagePattern } from '@nestjs/microservices';
|
|
1066
|
-
|
|
1067
|
-
@UseInterceptors(new KafkaTraceInterceptor())
|
|
1068
|
-
@Controller()
|
|
1069
|
-
export class OrdersConsumer {
|
|
1070
|
-
@EventPattern('order.created')
|
|
1071
|
-
async handle(@Payload() data: OrderCreatedEvent) {
|
|
1072
|
-
// getCurrentTraceId() works here — extracted from the Kafka message body/headers
|
|
1073
|
-
await logger.info('Processing order event', { orderId: data.orderId });
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
```
|
|
1077
|
-
|
|
1078
|
-
> **Note:** Pass `new KafkaTraceInterceptor()` (an instance), not the class reference. The interceptor's constructor takes an optional config and is not a NestJS DI provider.
|
|
1079
|
-
|
|
1080
|
-
### WebSocket trace interceptor
|
|
1081
|
-
|
|
1082
|
-
Propagates trace IDs through WebSocket event handlers. Reads `traceId` from the message body, event payload, or handshake query:
|
|
1083
|
-
|
|
1084
|
-
```typescript
|
|
1085
|
-
import { WebSocketTraceInterceptor } from 'logixia/nest';
|
|
1086
|
-
import { UseInterceptors, WebSocketGateway, SubscribeMessage } from '@nestjs/websockets';
|
|
1087
|
-
|
|
1088
|
-
@UseInterceptors(new WebSocketTraceInterceptor())
|
|
1089
|
-
@WebSocketGateway({ namespace: '/events', cors: { origin: '*' } })
|
|
1090
|
-
export class EventsGateway {
|
|
1091
|
-
@SubscribeMessage('ping')
|
|
1092
|
-
async handlePing(@MessageBody() data: { traceId?: string }) {
|
|
1093
|
-
// trace ID propagated from the WS message body / handshake headers
|
|
1094
|
-
await logger.info('WS ping received', { traceId: getCurrentTraceId() });
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
```
|
|
1098
|
-
|
|
1099
|
-
> **Note:** Pass `new WebSocketTraceInterceptor()` (an instance), not the class reference. `UseInterceptors` must be imported from `@nestjs/common`, not `@nestjs/websockets`.
|
|
1100
|
-
|
|
1101
|
-
````
|
|
1102
|
-
|
|
1103
|
-
---
|
|
1104
|
-
|
|
1105
|
-
## NestJS integration
|
|
1106
|
-
|
|
1107
|
-
> **Upgrading from 1.x:** since 2.0.0, everything NestJS-specific lives in `logixia/nest` only.
|
|
1108
|
-
> Change `import { LogixiaLoggerModule, LogixiaLoggerService, LogixiaExceptionFilter, InjectLogger, LogMethod } from 'logixia'`
|
|
1109
|
-
> to `... from 'logixia/nest'`. The root `logixia` entry no longer loads `@nestjs/common`, so plain Node, Express and Fastify apps
|
|
1110
|
-
> work without NestJS installed.
|
|
1111
|
-
|
|
1112
|
-
Drop-in module with zero boilerplate. Registers `TraceMiddleware` for all routes, provides `LogixiaLoggerService`, `KafkaTraceInterceptor`, and `WebSocketTraceInterceptor` via the global DI container.
|
|
1113
|
-
|
|
1114
|
-
> **Full working example** — see [`examples/nestjs-app/`](./examples/nestjs-app/) for a complete NestJS app with Docker Compose (Postgres, MongoDB, Kafka, Kafdrop) that exercises every feature: `LogixiaLoggerModule`, `TraceMiddleware`, `LogixiaExceptionFilter`, `HttpLoggingInterceptor`, `WebSocketTraceInterceptor`, `KafkaTraceInterceptor`, `@LogMethod`, `child()` loggers, `timeAsync`, real Kafka producer + consumer.
|
|
1115
|
-
>
|
|
1116
|
-
> ```bash
|
|
1117
|
-
> cd examples/nestjs-app
|
|
1118
|
-
> cp .env.example .env
|
|
1119
|
-
> docker compose up -d
|
|
1120
|
-
> curl http://localhost:3000/health
|
|
1121
|
-
> ```
|
|
1122
|
-
|
|
1123
|
-
```typescript
|
|
1124
|
-
// app.module.ts
|
|
1125
|
-
import { Module } from '@nestjs/common';
|
|
1126
|
-
import { LogixiaLoggerModule } from 'logixia/nest';
|
|
1127
|
-
|
|
1128
|
-
@Module({
|
|
1129
|
-
imports: [
|
|
1130
|
-
LogixiaLoggerModule.forRoot({
|
|
1131
|
-
appName: 'nestjs-api',
|
|
1132
|
-
environment: process.env.NODE_ENV ?? 'development',
|
|
1133
|
-
traceId: true,
|
|
1134
|
-
transports: {
|
|
1135
|
-
console: {},
|
|
1136
|
-
file: { filename: 'app.log', dirname: './logs', maxSize: '50MB' },
|
|
1137
|
-
},
|
|
1138
|
-
}),
|
|
1139
|
-
],
|
|
1140
|
-
})
|
|
1141
|
-
export class AppModule {}
|
|
1142
|
-
````
|
|
1143
|
-
|
|
1144
|
-
**Async configuration** (for credentials from a config service):
|
|
1145
|
-
|
|
1146
|
-
```typescript
|
|
1147
|
-
LogixiaLoggerModule.forRootAsync({
|
|
1148
|
-
imports: [ConfigModule],
|
|
1149
|
-
useFactory: async (config: ConfigService) => ({
|
|
1150
|
-
appName: 'nestjs-api',
|
|
1151
|
-
environment: config.get('NODE_ENV'),
|
|
1152
|
-
traceId: true,
|
|
1153
|
-
transports: {
|
|
1154
|
-
database: {
|
|
1155
|
-
type: 'postgresql',
|
|
1156
|
-
host: config.get('DB_HOST'),
|
|
1157
|
-
database: config.get('DB_NAME'),
|
|
1158
|
-
password: config.get('DB_PASSWORD'),
|
|
1159
|
-
table: 'logs',
|
|
1160
|
-
},
|
|
1161
|
-
},
|
|
1162
|
-
}),
|
|
1163
|
-
inject: [ConfigService],
|
|
1164
|
-
});
|
|
1165
|
-
```
|
|
1166
|
-
|
|
1167
|
-
**Inject the logger** in any service or controller. Since `LogixiaLoggerModule` is globally scoped, no per-module import is needed:
|
|
1168
|
-
|
|
1169
|
-
```typescript
|
|
1170
|
-
// orders.service.ts
|
|
1171
|
-
import { Injectable } from '@nestjs/common';
|
|
1172
|
-
import { LogixiaLoggerService } from 'logixia/nest';
|
|
1173
|
-
|
|
1174
|
-
@Injectable()
|
|
1175
|
-
export class OrdersService {
|
|
1176
|
-
constructor(private readonly logger: LogixiaLoggerService) {}
|
|
1177
|
-
|
|
1178
|
-
async createOrder(dto: CreateOrderDto) {
|
|
1179
|
-
await this.logger.info('Creating order', { userId: dto.userId });
|
|
1180
|
-
// ...
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
```
|
|
1184
|
-
|
|
1185
|
-
**Feature-scoped child logger** — create a logger pre-scoped to a specific context string:
|
|
1186
|
-
|
|
1187
|
-
```typescript
|
|
1188
|
-
// orders.module.ts
|
|
1189
|
-
import { Module } from '@nestjs/common';
|
|
1190
|
-
import { LogixiaLoggerModule } from 'logixia/nest';
|
|
1191
|
-
import { OrdersService } from './orders.service';
|
|
1192
|
-
|
|
1193
|
-
@Module({
|
|
1194
|
-
imports: [LogixiaLoggerModule.forFeature('OrdersModule')],
|
|
1195
|
-
providers: [OrdersService],
|
|
1196
|
-
})
|
|
1197
|
-
export class OrdersModule {}
|
|
1198
|
-
```
|
|
1199
|
-
|
|
1200
|
-
```typescript
|
|
1201
|
-
// orders.service.ts — inject the feature-scoped token
|
|
1202
|
-
import { Inject, Injectable } from '@nestjs/common';
|
|
1203
|
-
import { LOGIXIA_LOGGER_PREFIX, LogixiaLoggerService } from 'logixia/nest';
|
|
1204
|
-
|
|
1205
|
-
@Injectable()
|
|
1206
|
-
export class OrdersService {
|
|
1207
|
-
constructor(
|
|
1208
|
-
@Inject(`${LOGIXIA_LOGGER_PREFIX}ORDERSMODULE`)
|
|
1209
|
-
private readonly logger: LogixiaLoggerService
|
|
1210
|
-
) {}
|
|
1211
|
-
}
|
|
1212
|
-
```
|
|
1213
|
-
|
|
1214
|
-
`LogixiaLoggerService` exposes the full `LogixiaLogger` API: `info`, `warn`, `error`, `debug`, `trace`, `verbose`, `logLevel`, `time`, `timeEnd`, `timeAsync`, `setLevel`, `getLevel`, `setContext`, `child`, `close`, `getCurrentTraceId`, and more.
|
|
1215
|
-
|
|
1216
|
-
**Custom level proxy methods** — every key you add to `levelOptions.levels` automatically becomes a method on the service instance. Use `LogixiaLoggerService.create<T>(config)` (instead of `new`) to get full IntelliSense for those methods:
|
|
1217
|
-
|
|
1218
|
-
```typescript
|
|
1219
|
-
const logger = LogixiaLoggerService.create({
|
|
1220
|
-
levelOptions: {
|
|
1221
|
-
levels: { error: 0, warn: 1, info: 2, kafka: 2, mysql: 2, payment: 1 },
|
|
1222
|
-
colors: {
|
|
1223
|
-
error: 'red',
|
|
1224
|
-
warn: 'yellow',
|
|
1225
|
-
info: 'blue',
|
|
1226
|
-
kafka: 'magenta',
|
|
1227
|
-
payment: 'brightYellow',
|
|
1228
|
-
},
|
|
1229
|
-
},
|
|
1230
|
-
});
|
|
1231
|
-
|
|
1232
|
-
// Methods are created at construction time — no casting required
|
|
1233
|
-
await logger.kafka('Consumer rebalanced', { groupId: 'app-group' });
|
|
1234
|
-
await logger.mysql('Slow query detected', { query, ms: 1240 });
|
|
1235
|
-
await logger.payment('Charge captured', { txnId, amount: 99.99 });
|
|
1236
|
-
```
|
|
1237
|
-
|
|
1238
|
-
**TraceId** — with `traceId: true`, every log line carries a correlation ID automatically. Use `runWithTraceId()` to scope a trace to a block (the `TraceMiddleware` does this per-request automatically):
|
|
1239
|
-
|
|
1240
|
-
```typescript
|
|
1241
|
-
import { runWithTraceId } from 'logixia';
|
|
1242
|
-
|
|
1243
|
-
await runWithTraceId(String(req.headers['x-request-id']), async () => {
|
|
1244
|
-
await logger.info('Request received'); // traceId: "abc-123" in every line
|
|
1245
|
-
await logger.kafka('Event published'); // same traceId
|
|
1246
|
-
});
|
|
1247
|
-
|
|
1248
|
-
// Read the active traceId at any point
|
|
1249
|
-
const traceId = logger.getCurrentTraceId();
|
|
1250
|
-
```
|
|
1251
|
-
|
|
1252
|
-
### @LogMethod decorator
|
|
1253
|
-
|
|
1254
|
-
Automatically logs method entry, exit, duration, and errors — no manual `try/catch` or `logger.debug` calls needed. Works on both sync and async methods. Reads the `logger` property from the class instance (the NestJS convention).
|
|
1255
|
-
|
|
1256
|
-
```typescript
|
|
1257
|
-
import { Injectable } from '@nestjs/common';
|
|
1258
|
-
import { LogixiaLoggerService, LogMethod } from 'logixia/nest';
|
|
1259
|
-
|
|
1260
|
-
@Injectable()
|
|
1261
|
-
export class PaymentService {
|
|
1262
|
-
constructor(private readonly logger: LogixiaLoggerService) {}
|
|
1263
|
-
|
|
1264
|
-
// Logs entry with args, exit with duration, and errors with full stack trace
|
|
1265
|
-
@LogMethod({ level: 'info', logArgs: true, logResult: false })
|
|
1266
|
-
async processPayment(orderId: string, amount: number): Promise<void> {
|
|
1267
|
-
// your business logic — no try/catch needed for logging
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
// Minimal — just tracks duration at debug level
|
|
1271
|
-
@LogMethod()
|
|
1272
|
-
async fetchExchangeRate(currency: string): Promise<number> {
|
|
1273
|
-
return 1.0;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
```
|
|
1277
|
-
|
|
1278
|
-
`@LogMethod` options:
|
|
1279
|
-
|
|
1280
|
-
| Option | Type | Default | Description |
|
|
1281
|
-
| ----------- | -------------------------------- | --------- | ------------------------------------------------------- |
|
|
1282
|
-
| `level` | `'debug' \| 'info' \| 'verbose'` | `'debug'` | Log level for entry / exit messages |
|
|
1283
|
-
| `logArgs` | `boolean` | `true` | Include method arguments in the entry log |
|
|
1284
|
-
| `logResult` | `boolean` | `false` | Include the return value in the exit log |
|
|
1285
|
-
| `logErrors` | `boolean` | `true` | Log errors with stack trace when the method throws |
|
|
1286
|
-
| `label` | `string` | auto | Override the auto-detected `ClassName.methodName` label |
|
|
1287
|
-
|
|
1288
|
-
### LogixiaExceptionFilter
|
|
1289
|
-
|
|
1290
|
-
A global NestJS exception filter that automatically logs unhandled exceptions — HTTP exceptions as `warn`, everything else as `error` — and returns a consistent JSON error shape. Reads the injected `LogixiaLoggerService`; works even without it.
|
|
1291
|
-
|
|
1292
|
-
```typescript
|
|
1293
|
-
// main.ts
|
|
1294
|
-
import { NestFactory } from '@nestjs/core';
|
|
1295
|
-
import { LogixiaExceptionFilter } from 'logixia/nest';
|
|
1296
|
-
import { AppModule } from './app.module';
|
|
1297
|
-
|
|
1298
|
-
async function bootstrap() {
|
|
1299
|
-
const app = await NestFactory.create(AppModule);
|
|
1300
|
-
app.useGlobalFilters(new LogixiaExceptionFilter());
|
|
1301
|
-
await app.listen(3000);
|
|
1302
|
-
}
|
|
1303
|
-
bootstrap();
|
|
1304
|
-
```
|
|
1305
|
-
|
|
1306
|
-
With `LogixiaLoggerModule` set up, inject the service directly so it logs to your configured transports:
|
|
1307
|
-
|
|
1308
|
-
```typescript
|
|
1309
|
-
import { APP_FILTER } from '@nestjs/core';
|
|
1310
|
-
import { LogixiaExceptionFilter, LogixiaLoggerService } from 'logixia/nest';
|
|
1311
|
-
|
|
1312
|
-
// In AppModule providers:
|
|
1313
|
-
{
|
|
1314
|
-
provide: APP_FILTER,
|
|
1315
|
-
useFactory: (logger: LogixiaLoggerService) => new LogixiaExceptionFilter(logger),
|
|
1316
|
-
inject: [LogixiaLoggerService],
|
|
1317
|
-
}
|
|
1318
|
-
```
|
|
1319
|
-
|
|
1320
|
-
The filter returns a consistent structured shape on every error:
|
|
1321
|
-
|
|
1322
|
-
```json
|
|
1323
|
-
{
|
|
1324
|
-
"success": false,
|
|
1325
|
-
"error": {
|
|
1326
|
-
"type": "validation_error",
|
|
1327
|
-
"code": "ORD-001",
|
|
1328
|
-
"message": "Order amount must be greater than zero.",
|
|
1329
|
-
"param": "amount"
|
|
1330
|
-
},
|
|
1331
|
-
"meta": {
|
|
1332
|
-
"request_id": "req_8579ef8ff7a64c3cbc752fd1cb9852df",
|
|
1333
|
-
"timestamp": "2026-03-24T16:30:19.217Z",
|
|
1334
|
-
"path": "/orders/boom",
|
|
1335
|
-
"status": 400
|
|
1336
|
-
},
|
|
1337
|
-
"debug": {
|
|
1338
|
-
"stack": "LogixiaException: Order amount must be greater than zero.\n at ..."
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
```
|
|
1342
|
-
|
|
1343
|
-
The `debug` block (stack trace, service, duration) is **off by default** and is never sent to clients unless you opt in, for example in local development only:
|
|
1344
|
-
|
|
1345
|
-
```typescript
|
|
1346
|
-
new LogixiaExceptionFilter(undefined, undefined, {
|
|
1347
|
-
exposeDebug: process.env.NODE_ENV === 'development',
|
|
1348
|
-
// exposeCause: true, // also include the underlying error message (may contain internal detail)
|
|
1349
|
-
});
|
|
1350
|
-
```
|
|
1351
|
-
|
|
1352
|
-
And every log line carries `method`, `url`, `status`, `request_id` as structured fields — not a plain string:
|
|
1353
|
-
|
|
1354
|
-
```
|
|
1355
|
-
WARN [NestApplication] (traceId) [ORD-001] Order amount must be greater than zero.
|
|
1356
|
-
{ method: "GET", url: "/orders/boom", status: 400, request_id: "req_..." }
|
|
1357
|
-
|
|
1358
|
-
ERROR [NestApplication] (traceId) DB connection timed out after 5000ms
|
|
1359
|
-
{ method: "GET", url: "/orders/crash", status: 500, request_id: "req_...", error: { ... } }
|
|
1360
|
-
```
|
|
1361
|
-
|
|
1362
|
-
---
|
|
1363
|
-
|
|
1364
|
-
## Correlation ID propagation
|
|
1365
|
-
|
|
1366
|
-
`import { ... } from 'logixia/correlation'`
|
|
1367
|
-
|
|
1368
|
-
In a microservice architecture each incoming request should carry a `correlationId` that flows through every downstream service call, message queue event, and log line — so you can reconstruct the full request trace in any log aggregator.
|
|
1369
|
-
|
|
1370
|
-
logixia ships this as a dedicated `logixia/correlation` sub-package. It uses the same `AsyncLocalStorage` store as the main logger, so every `logger.*` call inside a correlated context automatically includes the ID.
|
|
1371
|
-
|
|
1372
|
-
### Correlation Express middleware
|
|
1373
|
-
|
|
1374
|
-
```typescript
|
|
1375
|
-
import { correlationMiddleware } from 'logixia/correlation';
|
|
1376
|
-
|
|
1377
|
-
// Zero-config — reads X-Correlation-ID from the incoming request.
|
|
1378
|
-
// Generates a UUID v4 if the header is missing or invalid.
|
|
1379
|
-
// Sets X-Correlation-ID on the response.
|
|
1380
|
-
app.use(correlationMiddleware());
|
|
1381
|
-
|
|
1382
|
-
// Custom config:
|
|
1383
|
-
app.use(
|
|
1384
|
-
correlationMiddleware({
|
|
1385
|
-
header: 'X-Correlation-ID', // header to read / write (matched case-insensitively). Default: 'x-correlation-id'
|
|
1386
|
-
generate: () => crypto.randomUUID(), // used when no usable incoming ID. Default: crypto.randomUUID()
|
|
1387
|
-
trustIncoming: true, // honour the header from the client. Default: true
|
|
1388
|
-
setResponseHeader: true, // echo the ID back in the response. Default: true
|
|
1389
|
-
})
|
|
1390
|
-
);
|
|
1391
|
-
```
|
|
1392
|
-
|
|
1393
|
-
An incoming ID is only used if it is at most 128 characters long and contains only
|
|
1394
|
-
`A-Z`, `a-z`, `0-9`, `.`, `_`, `:` and `-`. Anything else (newlines, spaces, or base64-style
|
|
1395
|
-
`+`, `/`, `=`) is replaced with a freshly generated ID, so client-supplied text can't end up in
|
|
1396
|
-
every log line. Set `trustIncoming: false` to ignore the incoming header entirely and always
|
|
1397
|
-
generate a new ID. `correlationFastifyHook()` takes the same options.
|
|
1398
|
-
|
|
1399
|
-
### Correlation Fastify hook
|
|
1400
|
-
|
|
1401
|
-
```typescript
|
|
1402
|
-
import Fastify from 'fastify';
|
|
1403
|
-
import { correlationFastifyHook } from 'logixia/correlation';
|
|
1404
|
-
|
|
1405
|
-
const app = Fastify();
|
|
1406
|
-
app.addHook('onRequest', correlationFastifyHook());
|
|
1407
|
-
```
|
|
1408
|
-
|
|
1409
|
-
> **Full working example** — see [`examples/fastify-example.ts`](./examples/fastify-example.ts) (`npm run dev:fastify`) for request logging, trace IDs and redaction on Fastify.
|
|
1410
|
-
|
|
1411
|
-
### Outbound fetch / axios
|
|
1412
|
-
|
|
1413
|
-
Every outbound HTTP call made inside a correlated context automatically carries the `X-Correlation-ID` header.
|
|
1414
|
-
|
|
1415
|
-
**fetch:**
|
|
1416
|
-
|
|
1417
|
-
```typescript
|
|
1418
|
-
import { correlationFetch } from 'logixia/correlation';
|
|
1419
|
-
|
|
1420
|
-
// Drop-in replacement for global fetch — forwards the active correlation ID automatically
|
|
1421
|
-
const res = await correlationFetch('https://inventory-service/api/items', {
|
|
1422
|
-
method: 'GET',
|
|
1423
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
1424
|
-
});
|
|
1425
|
-
```
|
|
1426
|
-
|
|
1427
|
-
**axios:**
|
|
1428
|
-
|
|
1429
|
-
```typescript
|
|
1430
|
-
import axios from 'axios';
|
|
1431
|
-
import { createCorrelationAxiosInterceptor } from 'logixia/correlation';
|
|
1432
|
-
|
|
1433
|
-
const client = axios.create({ baseURL: 'https://inventory-service' });
|
|
1434
|
-
createCorrelationAxiosInterceptor(client); // attaches X-Correlation-ID to every request
|
|
1435
|
-
```
|
|
1436
|
-
|
|
1437
|
-
### Kafka / SQS helpers
|
|
1438
|
-
|
|
1439
|
-
```typescript
|
|
1440
|
-
import {
|
|
1441
|
-
buildKafkaCorrelationHeaders, // → { 'X-Correlation-ID': '...', 'X-Request-ID': '...' }
|
|
1442
|
-
extractMessageCorrelationId, // read correlationId from a Kafka/SQS message body
|
|
1443
|
-
childFromRequest, // create a child logger pre-seeded with request context
|
|
1444
|
-
withCorrelationId, // run a callback inside an explicit correlation context
|
|
1445
|
-
getCurrentCorrelationId, // read the active correlation ID (or undefined)
|
|
1446
|
-
generateCorrelationId, // generate a new UUID v4 correlation ID
|
|
1447
|
-
} from 'logixia/correlation';
|
|
1448
|
-
|
|
1449
|
-
// Kafka producer — attach correlation headers to every message
|
|
1450
|
-
const producer = kafka.producer();
|
|
1451
|
-
await producer.send({
|
|
1452
|
-
topic: 'orders',
|
|
1453
|
-
messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],
|
|
1454
|
-
});
|
|
1455
|
-
|
|
1456
|
-
// Kafka consumer — restore context for the handler
|
|
1457
|
-
const correlationId = extractMessageCorrelationId(message.value);
|
|
1458
|
-
withCorrelationId(correlationId, async () => {
|
|
1459
|
-
await orderService.process(message.value);
|
|
1460
|
-
// all logger.* calls inside carry correlationId automatically
|
|
1461
|
-
});
|
|
1462
|
-
|
|
1463
|
-
// Create a child logger pre-loaded with request identifiers
|
|
1464
|
-
const reqLogger = childFromRequest(logger, req);
|
|
1465
|
-
await reqLogger.info('Order created', { orderId: 'ord_123' });
|
|
1466
|
-
// → log includes correlationId, requestId, originService
|
|
1467
|
-
```
|
|
1468
|
-
|
|
1469
|
-
**Standalone context (no HTTP framework):**
|
|
1470
|
-
|
|
1471
|
-
```typescript
|
|
1472
|
-
import { withCorrelationId, generateCorrelationId } from 'logixia/correlation';
|
|
1473
|
-
|
|
1474
|
-
const id = generateCorrelationId(); // UUID v4
|
|
1475
|
-
|
|
1476
|
-
withCorrelationId(id, async () => {
|
|
1477
|
-
await processJob(job);
|
|
1478
|
-
// all nested logger calls carry id
|
|
1479
|
-
});
|
|
1480
|
-
```
|
|
1481
|
-
|
|
1482
|
-
---
|
|
1483
|
-
|
|
1484
|
-
## Browser support
|
|
1485
|
-
|
|
1486
|
-
`import { ... } from 'logixia/browser'`
|
|
1487
|
-
|
|
1488
|
-
The `logixia/browser` entry point is a fully tree-shakeable, Node.js-free logger for browsers, Cloudflare Workers, Deno, Bun, and any other non-Node runtime. It has zero imports from `node:fs`, `node:async_hooks`, `node:worker_threads`, or any other Node.js built-in.
|
|
1489
|
-
|
|
1490
|
-
```typescript
|
|
1491
|
-
import { createBrowserLogger } from 'logixia/browser';
|
|
1492
|
-
|
|
1493
|
-
const logger = createBrowserLogger({
|
|
1494
|
-
appName: 'my-app',
|
|
1495
|
-
minLevel: 'info',
|
|
1496
|
-
pretty: true, // colorized dev-friendly output via console.group
|
|
1497
|
-
});
|
|
1498
|
-
|
|
1499
|
-
logger.info('App loaded', { route: '/home' });
|
|
1500
|
-
logger.warn('Feature flag missing', { flag: 'new-checkout' });
|
|
1501
|
-
logger.error('API call failed', { url: '/api/orders', status: 500 });
|
|
1502
|
-
```
|
|
1503
|
-
|
|
1504
|
-
**Browser console transport** — uses the native `console` API and maps levels to their correct methods (`console.error`, `console.warn`, `console.info`, `console.debug`):
|
|
1505
|
-
|
|
1506
|
-
```typescript
|
|
1507
|
-
import { BrowserLogger, BrowserConsoleTransport } from 'logixia/browser';
|
|
1508
|
-
|
|
1509
|
-
const logger = new BrowserLogger({
|
|
1510
|
-
appName: 'my-app',
|
|
1511
|
-
transports: [new BrowserConsoleTransport({ pretty: true })],
|
|
1512
|
-
});
|
|
1513
|
-
```
|
|
1514
|
-
|
|
1515
|
-
**Remote batch transport** — buffers log entries and ships them to a remote endpoint in batches. Zero `XMLHttpRequest` or Node.js `http` — uses the global `fetch` API:
|
|
1516
|
-
|
|
1517
|
-
```typescript
|
|
1518
|
-
import { BrowserLogger, BrowserRemoteTransport } from 'logixia/browser';
|
|
1519
|
-
|
|
1520
|
-
const logger = new BrowserLogger({
|
|
1521
|
-
appName: 'my-app',
|
|
1522
|
-
transports: [
|
|
1523
|
-
new BrowserRemoteTransport({
|
|
1524
|
-
endpoint: 'https://logs.my-company.com/ingest',
|
|
1525
|
-
batchSize: 20, // flush when 20 entries accumulate
|
|
1526
|
-
flushIntervalMs: 5000, // or every 5 seconds, whichever comes first
|
|
1527
|
-
headers: { Authorization: `Bearer ${TOKEN}` },
|
|
1528
|
-
minLevel: 'warn', // only ship warn+ to the remote endpoint
|
|
1529
|
-
}),
|
|
1530
|
-
],
|
|
1531
|
-
});
|
|
1532
|
-
```
|
|
1533
|
-
|
|
1534
|
-
**Cloudflare Workers** — see [`examples/cloudflare-worker/`](./examples/cloudflare-worker/) for a Worker that logs JSON, tags each request with `child({ requestId })` and flushes a `BrowserRemoteTransport` with `ctx.waitUntil()`. Pass `flushIntervalMs: 0` to the transport: Workers don't allow timers at global scope.
|
|
1535
|
-
|
|
1536
|
-
The following utilities from the main package are also re-exported from `logixia/browser` (safe for non-Node runtimes):
|
|
1537
|
-
|
|
1538
|
-
```typescript
|
|
1539
|
-
import {
|
|
1540
|
-
createTypedLogger, // typed schema-enforced logger factory
|
|
1541
|
-
defineLogSchema, // define a compile-time log schema
|
|
1542
|
-
createOtelBridge, // OpenTelemetry bridge
|
|
1543
|
-
isOtelActive,
|
|
1544
|
-
withOtelSpan,
|
|
1545
|
-
} from 'logixia/browser';
|
|
1546
|
-
```
|
|
1547
|
-
|
|
1548
|
-
---
|
|
1549
|
-
|
|
1550
|
-
## Log redaction
|
|
1551
|
-
|
|
1552
|
-
Redact sensitive fields before they reach **any** transport — passwords, tokens, PII, credit card numbers. Redaction is applied once before dispatch; no transport can accidentally log sensitive data. The original object is never mutated.
|
|
1553
|
-
|
|
1554
|
-
**Path-based redaction** supports dot-notation, `*` (single segment wildcard), and `**` (any-depth wildcard):
|
|
1555
|
-
|
|
1556
|
-
```typescript
|
|
1557
|
-
const logger = createLogger({
|
|
1558
|
-
appName: 'api',
|
|
1559
|
-
environment: 'production',
|
|
1560
|
-
redact: {
|
|
1561
|
-
paths: [
|
|
1562
|
-
'password',
|
|
1563
|
-
'token',
|
|
1564
|
-
'accessToken',
|
|
1565
|
-
'refreshToken',
|
|
1566
|
-
'*.secret', // any field named 'secret' at one level deep
|
|
1567
|
-
'req.headers.*', // all headers
|
|
1568
|
-
'user.creditCard', // nested path
|
|
1569
|
-
'**.password', // 'password' at any depth
|
|
1570
|
-
],
|
|
1571
|
-
censor: '[REDACTED]', // default if omitted
|
|
1572
|
-
},
|
|
1573
|
-
});
|
|
1574
|
-
|
|
1575
|
-
await logger.info('User login', {
|
|
1576
|
-
username: 'alice',
|
|
1577
|
-
password: 'hunter2', // → '[REDACTED]'
|
|
1578
|
-
token: 'eyJhbGc...', // → '[REDACTED]'
|
|
1579
|
-
user: {
|
|
1580
|
-
creditCard: '4111...', // → '[REDACTED]'
|
|
1581
|
-
email: 'alice@example.com', // untouched
|
|
1582
|
-
},
|
|
1583
|
-
});
|
|
1584
|
-
```
|
|
1585
|
-
|
|
1586
|
-
**Regex-based redaction** — mask patterns in string values across all fields:
|
|
1587
|
-
|
|
1588
|
-
```typescript
|
|
1589
|
-
const logger = createLogger({
|
|
1590
|
-
appName: 'api',
|
|
1591
|
-
environment: 'production',
|
|
1592
|
-
redact: {
|
|
1593
|
-
patterns: [
|
|
1594
|
-
/Bearer\s+\S+/gi, // Authorization header values
|
|
1595
|
-
/sk-[a-z0-9]{32,}/gi, // OpenAI / Stripe secret keys
|
|
1596
|
-
/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, // credit card numbers
|
|
1597
|
-
],
|
|
1598
|
-
},
|
|
1599
|
-
});
|
|
1600
|
-
```
|
|
1601
|
-
|
|
1602
|
-
Both `paths` and `patterns` can be combined in the same config.
|
|
1603
|
-
|
|
1604
|
-
Path rules follow these conventions:
|
|
1605
|
-
|
|
1606
|
-
- Matching is case-insensitive, so `req.headers.authorization` also redacts `Authorization`.
|
|
1607
|
-
- Array elements are addressed by index: `users.*.password` and `users.0.password` both match, and the older index-free form `users.password` still works. Nested arrays are walked the same way.
|
|
1608
|
-
- Circular references are printed as `[Circular]` instead of throwing.
|
|
1609
|
-
- Plain objects, class instances (ORM entities, documents) and arrays are walked. Built-ins such as `URL`, `Date`, `Buffer`, `Map`, `Set` and `Error` pass through unchanged unless a rule targets them directly.
|
|
1610
|
-
|
|
1611
|
-
---
|
|
1612
|
-
|
|
1613
|
-
## Timer API
|
|
1614
|
-
|
|
1615
|
-
Measure the duration of any operation — synchronous or async. The result is logged automatically when the timer ends:
|
|
1616
|
-
|
|
1617
|
-
```typescript
|
|
1618
|
-
// Manual start/stop
|
|
1619
|
-
logger.time('db-query');
|
|
1620
|
-
const rows = await db.query('SELECT * FROM orders');
|
|
1621
|
-
await logger.timeEnd('db-query');
|
|
1622
|
-
// → logs: Timer 'db-query' finished { duration: '42ms', startTime: '...', endTime: '...' }
|
|
1623
|
-
|
|
1624
|
-
// Wrap an async function — timer starts before and stops after, even if the function throws
|
|
1625
|
-
const result = await logger.timeAsync('process-batch', async () => {
|
|
1626
|
-
return await processBatch(items);
|
|
1627
|
-
});
|
|
1628
|
-
```
|
|
1629
|
-
|
|
1630
|
-
`timeEnd` returns the duration in milliseconds so you can use it in your own logic:
|
|
1631
|
-
|
|
1632
|
-
```typescript
|
|
1633
|
-
const ms = await logger.timeEnd('db-query');
|
|
1634
|
-
if (ms && ms > 500) {
|
|
1635
|
-
await logger.warn('Slow query detected', { durationMs: ms });
|
|
1636
|
-
}
|
|
1637
|
-
```
|
|
1638
|
-
|
|
1639
|
-
---
|
|
1640
|
-
|
|
1641
|
-
## Field management
|
|
1642
|
-
|
|
1643
|
-
Control which fields appear in log output at runtime, without changing config:
|
|
1644
|
-
|
|
1645
|
-
```typescript
|
|
1646
|
-
// Disable fields you don't need in a specific context
|
|
1647
|
-
logger.disableField('traceId');
|
|
1648
|
-
logger.disableField('appName');
|
|
1649
|
-
|
|
1650
|
-
// Re-enable them
|
|
1651
|
-
logger.enableField('traceId');
|
|
1652
|
-
|
|
1653
|
-
// Check whether a field is currently active
|
|
1654
|
-
const isOn = logger.isFieldEnabled('timestamp'); // true
|
|
1655
|
-
|
|
1656
|
-
// Inspect the current state of all fields
|
|
1657
|
-
const state = logger.getFieldState();
|
|
1658
|
-
// → { timestamp: true, level: true, appName: false, traceId: false, ... }
|
|
1659
|
-
|
|
1660
|
-
// Reset all fields back to the config defaults
|
|
1661
|
-
logger.resetFieldState();
|
|
1662
|
-
```
|
|
1663
|
-
|
|
1664
|
-
Available field names: `timestamp`, `level`, `appName`, `service`, `traceId`, `message`, `payload`, `timeTaken`, `context`, `requestId`, `userId`, `sessionId`, `environment`.
|
|
1665
|
-
|
|
1666
|
-
---
|
|
1667
|
-
|
|
1668
|
-
## Transport level control
|
|
1669
|
-
|
|
1670
|
-
By default, every transport receives every log entry that passes the global level filter. You can narrow a specific transport to only receive a subset of levels:
|
|
1671
|
-
|
|
1672
|
-
```typescript
|
|
1673
|
-
// Only send errors to the database transport — no noise from info/debug
|
|
1674
|
-
logger.setTransportLevels('database-0', ['error', 'warn']);
|
|
1675
|
-
|
|
1676
|
-
// Check what levels a transport is currently configured for
|
|
1677
|
-
const levels = logger.getTransportLevels('database-0'); // ['error', 'warn']
|
|
1678
|
-
|
|
1679
|
-
// List all registered transport IDs
|
|
1680
|
-
const ids = logger.getAvailableTransports(); // ['console', 'file-0', 'database-0']
|
|
1681
|
-
|
|
1682
|
-
// Remove all level overrides — all transports receive everything again
|
|
1683
|
-
logger.clearTransportLevelPreferences();
|
|
1684
|
-
```
|
|
1685
|
-
|
|
1686
|
-
## Transport filter predicate
|
|
1687
|
-
|
|
1688
|
-
Every transport (database, file, console, analytics, custom) accepts an optional `filter` function. It runs after the level check and lets you express any condition over the full log entry — by context, message content, payload fields, or anything else. Return `false` to skip the entry for that transport only; other transports are unaffected.
|
|
1689
|
-
|
|
1690
|
-
```typescript
|
|
1691
|
-
const logger = createLogger({
|
|
1692
|
-
appName: 'api',
|
|
1693
|
-
environment: 'production',
|
|
1694
|
-
transports: {
|
|
1695
|
-
// Only persist error/warn entries that come from PaymentService to the DB
|
|
1696
|
-
database: {
|
|
1697
|
-
type: 'mongodb',
|
|
1698
|
-
connectionString: process.env.MONGO_URI,
|
|
1699
|
-
database: 'appdb',
|
|
1700
|
-
collection: 'logs',
|
|
1701
|
-
filter: (entry) =>
|
|
1702
|
-
['error', 'warn'].includes(entry.level) && entry.context === 'PaymentService',
|
|
1703
|
-
},
|
|
1704
|
-
|
|
1705
|
-
// Write everything to the main file, but exclude noisy health-check pings
|
|
1706
|
-
file: {
|
|
1707
|
-
filename: 'app.log',
|
|
1708
|
-
dirname: './logs',
|
|
1709
|
-
filter: (entry) => entry.message !== 'health check',
|
|
1710
|
-
},
|
|
1711
|
-
|
|
1712
|
-
// Console gets everything — no filter
|
|
1713
|
-
console: { colorize: true },
|
|
1714
|
-
},
|
|
1715
|
-
});
|
|
1716
|
-
```
|
|
1717
|
-
|
|
1718
|
-
The `filter` predicate receives a `TransportLogEntry`:
|
|
1719
|
-
|
|
1720
|
-
```typescript
|
|
1721
|
-
interface TransportLogEntry {
|
|
1722
|
-
timestamp: Date;
|
|
1723
|
-
level: string; // 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'verbose' | string
|
|
1724
|
-
message: string;
|
|
1725
|
-
data?: Record<string, unknown>;
|
|
1726
|
-
context?: string; // set via logger.child('MyService') or setContext()
|
|
1727
|
-
traceId?: string;
|
|
1728
|
-
appName?: string;
|
|
1729
|
-
environment?: string;
|
|
1730
|
-
}
|
|
1731
|
-
```
|
|
1732
|
-
|
|
1733
|
-
`filter` and `level` are independent — both must pass for an entry to be written. Use `level` for threshold filtering (everything at or above `warn`) and `filter` for precise, field-level rules.
|
|
1734
|
-
|
|
1735
|
-
---
|
|
1736
|
-
|
|
1737
|
-
## Log search
|
|
1738
|
-
|
|
1739
|
-
Query your in-memory log history without shipping to Elasticsearch, Datadog, or any external service. Useful in development and lightweight production setups:
|
|
1740
|
-
|
|
1741
|
-
```typescript
|
|
1742
|
-
import { SearchManager } from 'logixia';
|
|
1743
|
-
|
|
1744
|
-
const search = new SearchManager({ maxEntries: 10_000 });
|
|
1745
|
-
|
|
1746
|
-
// Index a batch of entries (from a file, database query, or any source)
|
|
1747
|
-
await search.index(logEntries);
|
|
1748
|
-
|
|
1749
|
-
// Search by text query, level, and time range
|
|
1750
|
-
const results = await search.search({
|
|
1751
|
-
query: 'payment failed',
|
|
1752
|
-
level: 'error',
|
|
1753
|
-
from: new Date('2025-01-01'),
|
|
1754
|
-
to: new Date(),
|
|
1755
|
-
limit: 50,
|
|
1756
|
-
});
|
|
1757
|
-
// → sorted by relevance, full metadata included
|
|
1758
|
-
```
|
|
1759
|
-
|
|
1760
|
-
---
|
|
1761
|
-
|
|
1762
|
-
## OpenTelemetry
|
|
1763
|
-
|
|
1764
|
-
W3C `traceparent` and `tracestate` headers are extracted from incoming requests and attached to every log entry automatically — enabling correlation between distributed traces and log events in Jaeger, Zipkin, Honeycomb, Datadog, and similar tools:
|
|
1765
|
-
|
|
1766
|
-
```typescript
|
|
1767
|
-
const logger = createLogger({
|
|
1768
|
-
appName: 'checkout-service',
|
|
1769
|
-
environment: 'production',
|
|
1770
|
-
traceId: {
|
|
1771
|
-
enabled: true,
|
|
1772
|
-
extractor: {
|
|
1773
|
-
header: ['traceparent', 'tracestate', 'x-trace-id'],
|
|
1774
|
-
},
|
|
1775
|
-
},
|
|
1776
|
-
});
|
|
1777
|
-
|
|
1778
|
-
// The traceparent header from the incoming request is stored as the trace ID
|
|
1779
|
-
// and included in every log entry automatically.
|
|
1780
|
-
app.post('/checkout', async (req, res) => {
|
|
1781
|
-
await logger.info('Checkout initiated', { cartId: req.body.cartId });
|
|
1782
|
-
// → log carries the W3C traceparent from the request
|
|
1783
|
-
});
|
|
1784
|
-
```
|
|
1785
|
-
|
|
1786
|
-
---
|
|
1787
|
-
|
|
1788
|
-
## OTLP logs export (OpenTelemetry-native)
|
|
1789
|
-
|
|
1790
|
-
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.
|
|
1791
|
-
|
|
1792
|
-
```typescript
|
|
1793
|
-
import { OtlpLogTransport } from 'logixia';
|
|
1794
|
-
|
|
1795
|
-
const logger = createLogger({
|
|
1796
|
-
appName: 'api',
|
|
1797
|
-
transports: {
|
|
1798
|
-
custom: [
|
|
1799
|
-
new OtlpLogTransport({
|
|
1800
|
-
url: 'http://localhost:4318/v1/logs',
|
|
1801
|
-
serviceName: 'api',
|
|
1802
|
-
serviceVersion: '1.4.0',
|
|
1803
|
-
environment: 'production',
|
|
1804
|
-
headers: { 'x-api-key': process.env.OTLP_KEY! },
|
|
1805
|
-
}),
|
|
1806
|
-
],
|
|
1807
|
-
},
|
|
1808
|
-
});
|
|
1809
|
-
// Every log is exported as an OTel LogRecord with traceId/spanId for native
|
|
1810
|
-
// trace↔log correlation. Buffers drain on close() — no loss on shutdown.
|
|
1811
|
-
```
|
|
1812
|
-
|
|
1813
|
-
---
|
|
1814
|
-
|
|
1815
|
-
## Wide events / canonical log lines
|
|
1816
|
-
|
|
1817
|
-
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.
|
|
1818
|
-
|
|
1819
|
-
```typescript
|
|
1820
|
-
import { wideEventMiddleware, addEventFields } from 'logixia';
|
|
1821
|
-
|
|
1822
|
-
// One canonical line per request, auto-emitted on response finish/close:
|
|
1823
|
-
app.use(wideEventMiddleware(logger)); // pre-fills method, url, ip, status, duration
|
|
1824
|
-
|
|
1825
|
-
app.get('/checkout', (req, res) => {
|
|
1826
|
-
addEventFields({ userId: req.user.id, planTier: 'pro' }); // from anywhere
|
|
1827
|
-
addEventFields({ dbQueries: 4, cacheHit: true });
|
|
1828
|
-
res.json({ ok: true });
|
|
1829
|
-
// → ONE log line: { method, url, statusCode, durationMs, userId, planTier,
|
|
1830
|
-
// dbQueries, cacheHit, traceId } — no JOINs at query time
|
|
1831
|
-
});
|
|
1832
|
-
```
|
|
1833
|
-
|
|
1834
|
-
Or wrap any unit of work manually — the event is emitted once, even if the callback throws:
|
|
1835
|
-
|
|
1836
|
-
```typescript
|
|
1837
|
-
import { withWideEvent, addEventFields } from 'logixia';
|
|
1838
|
-
|
|
1839
|
-
await withWideEvent(logger, { job: 'reindex' }, async () => {
|
|
1840
|
-
addEventFields({ shard: 3 });
|
|
1841
|
-
await doWork(); // throws? → event still emitted with { error: true, errorMessage }
|
|
1842
|
-
});
|
|
1843
|
-
```
|
|
1844
|
-
|
|
1845
|
-
---
|
|
1846
|
-
|
|
1847
|
-
## Dynamic runtime reconfiguration
|
|
1848
|
-
|
|
1849
|
-
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.
|
|
1850
|
-
|
|
1851
|
-
```typescript
|
|
1852
|
-
import { registerLevelSignal, createLevelControlHandler } from 'logixia';
|
|
1853
|
-
|
|
1854
|
-
// 1. Per-namespace level, live:
|
|
1855
|
-
logger.setNamespaceLevels({ 'db.*': 'debug', '*': 'info' }); // db.* → debug now
|
|
1856
|
-
|
|
1857
|
-
// 2. Cycle the global level with one signal (kill -USR2 <pid>):
|
|
1858
|
-
registerLevelSignal(logger); // info → debug → trace → … → info
|
|
1859
|
-
|
|
1860
|
-
// 3. Ops endpoint (mount behind your auth):
|
|
1861
|
-
app.all('/admin/log-level', createLevelControlHandler(logger));
|
|
1862
|
-
// GET → { level, namespaceLevels }
|
|
1863
|
-
// POST { "level": "debug", "namespaceLevels": { "db.*": "trace" } }
|
|
1864
|
-
```
|
|
1865
|
-
|
|
1866
|
-
---
|
|
1867
|
-
|
|
1868
|
-
## Adaptive sampling
|
|
1869
|
-
|
|
1870
|
-
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.
|
|
1871
|
-
|
|
1872
|
-
```typescript
|
|
1873
|
-
const logger = createLogger({
|
|
1874
|
-
appName: 'api',
|
|
1875
|
-
sampling: {
|
|
1876
|
-
rate: 0.1, // keep 10% in steady state
|
|
1877
|
-
adaptive: {
|
|
1878
|
-
errorRateThreshold: 0.05, // ≥5% errors in the window…
|
|
1879
|
-
boostRate: 1.0, // …keep everything until it subsides
|
|
1880
|
-
windowMs: 10_000,
|
|
1881
|
-
},
|
|
1882
|
-
},
|
|
1883
|
-
});
|
|
1884
|
-
```
|
|
1885
|
-
|
|
1886
|
-
`maxLogsPerSecond` is always enforced after the rate decision, including with `traceConsistent: true` (a kept trace can still lose entries once the cap is hit). With `traceConsistent`, a trace's keep/drop decision is remembered in a bounded LRU (100k traces, dropped after 5 minutes idle) and survives stats resets; logs written outside any trace are sampled one by one.
|
|
1887
|
-
|
|
1888
|
-
---
|
|
1889
|
-
|
|
1890
|
-
## Robust serialization
|
|
1891
|
-
|
|
1892
|
-
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:
|
|
1893
|
-
|
|
1894
|
-
```typescript
|
|
1895
|
-
import { safeStringify, decycleValue, retrocycle } from 'logixia';
|
|
1896
|
-
|
|
1897
|
-
safeStringify({ id: 9007199254740993n, self: obj }); // BigInt + cycle safe
|
|
1898
|
-
const json = safeStringify(graph, { decycle: true }); // $ref pointers, not [Circular]
|
|
1899
|
-
const restored = retrocycle(JSON.parse(json)); // shared refs reconstructed
|
|
1900
|
-
```
|
|
1901
|
-
|
|
1902
|
-
---
|
|
1903
|
-
|
|
1904
|
-
## Graceful shutdown
|
|
1905
|
-
|
|
1906
|
-
Ensures all buffered log entries are flushed to every transport before the process exits. Critical for database and analytics transports that batch writes.
|
|
1907
|
-
|
|
1908
|
-
> **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.
|
|
1909
|
-
|
|
1910
|
-
The simplest approach is to set `gracefulShutdown: true` in config — logixia registers SIGTERM and SIGINT handlers automatically:
|
|
1911
|
-
|
|
1912
|
-
```typescript
|
|
1913
|
-
const logger = createLogger({
|
|
1914
|
-
appName: 'api',
|
|
1915
|
-
environment: 'production',
|
|
1916
|
-
gracefulShutdown: true,
|
|
1917
|
-
transports: { database: { type: 'postgresql' /* ... */ } },
|
|
1918
|
-
});
|
|
1919
|
-
// SIGTERM / SIGINT will flush all transports before exit. No extra code needed.
|
|
1920
|
-
```
|
|
1921
|
-
|
|
1922
|
-
For more control, pass a config object:
|
|
1923
|
-
|
|
1924
|
-
```typescript
|
|
1925
|
-
const logger = createLogger({
|
|
1926
|
-
appName: 'api',
|
|
1927
|
-
environment: 'production',
|
|
1928
|
-
gracefulShutdown: {
|
|
1929
|
-
enabled: true,
|
|
1930
|
-
timeout: 10_000, // wait up to 10 s; force-exits after
|
|
1931
|
-
signals: ['SIGTERM', 'SIGINT', 'SIGHUP'],
|
|
1932
|
-
exit: 'auto', // 'auto' | 'always' | 'never'
|
|
1933
|
-
},
|
|
1934
|
-
transports: {/* ... */},
|
|
1935
|
-
});
|
|
1936
|
-
```
|
|
1937
|
-
|
|
1938
|
-
**Who ends the process.** By default (`exit: 'auto'`) logixia flushes and then exits only if its handler is the only listener for that signal. If your app registers its own `SIGTERM` handler (`server.close()`, NestJS shutdown hooks, ...), logixia just flushes and leaves the exit to you, so your cleanup is never cut off. When logixia does exit it uses `128 + signal number` (143 for SIGTERM, 130 for SIGINT), so supervisors see a signal exit. Set `exit: 'always'` or `exit: 'never'` to override. Registered loggers are also flushed (not closed) on `beforeExit`, so an app that keeps logging after the event loop first drains still writes.
|
|
1939
|
-
|
|
1940
|
-
`flushOnExit` is configured by its first call; later calls with different `timeout`, `signals`, `exit` or hooks print a warning and are ignored.
|
|
1941
|
-
|
|
1942
|
-
You can also call `flushOnExit` directly with lifecycle hooks:
|
|
1943
|
-
|
|
1944
|
-
```typescript
|
|
1945
|
-
import { flushOnExit } from 'logixia';
|
|
1946
|
-
|
|
1947
|
-
flushOnExit({
|
|
1948
|
-
timeout: 5000,
|
|
1949
|
-
beforeFlush: async () => {
|
|
1950
|
-
// stop accepting new requests
|
|
1951
|
-
},
|
|
1952
|
-
afterFlush: async () => {
|
|
1953
|
-
// any cleanup after all logs are written
|
|
1954
|
-
},
|
|
1955
|
-
});
|
|
1956
|
-
```
|
|
1957
|
-
|
|
1958
|
-
Or flush and close manually — useful in Kubernetes SIGTERM handlers:
|
|
1959
|
-
|
|
1960
|
-
```typescript
|
|
1961
|
-
process.on('SIGTERM', async () => {
|
|
1962
|
-
// close() flushes all transports first, then closes connections.
|
|
1963
|
-
// Any log calls that arrive during the flush (e.g. from Kafka/MongoDB
|
|
1964
|
-
// disconnect handlers) are still written before the gate closes.
|
|
1965
|
-
await logger.close();
|
|
1966
|
-
process.exit(0);
|
|
1967
|
-
});
|
|
1968
|
-
```
|
|
1969
|
-
|
|
1970
|
-
For health monitoring:
|
|
1971
|
-
|
|
1972
|
-
```typescript
|
|
1973
|
-
const { healthy, details } = await logger.healthCheck();
|
|
1974
|
-
// → { healthy: true, details: { 'database-0': { ready: true, metrics: { logsWritten: 1042, ... } } } }
|
|
1975
|
-
```
|
|
1976
|
-
|
|
1977
|
-
---
|
|
1978
|
-
|
|
1979
|
-
## Plugin / extension API
|
|
1980
|
-
|
|
1981
|
-
logixia's plugin system lets you hook into every stage of the log lifecycle without touching core logger code. Plugins are plain objects that implement one or more lifecycle methods.
|
|
1982
|
-
|
|
1983
|
-
```typescript
|
|
1984
|
-
import type { LogixiaPlugin, LogEntry } from 'logixia';
|
|
1985
|
-
```
|
|
1986
|
-
|
|
1987
|
-
### Writing a plugin
|
|
1988
|
-
|
|
1989
|
-
```typescript
|
|
1990
|
-
const myPlugin: LogixiaPlugin = {
|
|
1991
|
-
name: 'my-plugin',
|
|
1992
|
-
|
|
1993
|
-
// Called once when the logger is constructed (global plugins)
|
|
1994
|
-
// or when .use() is called (per-logger plugins).
|
|
1995
|
-
onInit() {
|
|
1996
|
-
console.log('Plugin initialised');
|
|
1997
|
-
},
|
|
1998
|
-
|
|
1999
|
-
// Called for every log entry before it is formatted and written.
|
|
2000
|
-
// Return the (optionally mutated) entry to let it through,
|
|
2001
|
-
// or return null to silently drop it.
|
|
2002
|
-
onLog(entry: LogEntry): LogEntry | null {
|
|
2003
|
-
// Example: enrich every entry with a deployment tag
|
|
2004
|
-
return { ...entry, data: { ...entry.data, deployId: process.env.DEPLOY_ID } };
|
|
2005
|
-
},
|
|
2006
|
-
|
|
2007
|
-
// Called whenever logger.error() receives an Error object.
|
|
2008
|
-
onError(error: Error, entry?: LogEntry) {
|
|
2009
|
-
// Example: forward to a Sentry-compatible sink
|
|
2010
|
-
externalErrorTracker.capture(error, { extra: entry?.data });
|
|
2011
|
-
},
|
|
2012
|
-
|
|
2013
|
-
// Called during logger.close() — await-able for graceful teardown.
|
|
2014
|
-
async onShutdown() {
|
|
2015
|
-
await flushBufferedEvents();
|
|
2016
|
-
},
|
|
2017
|
-
};
|
|
2018
|
-
```
|
|
2019
|
-
|
|
2020
|
-
### Registering plugins globally
|
|
2021
|
-
|
|
2022
|
-
Plugins registered on `globalPluginRegistry` are automatically seeded into every logger created after the call.
|
|
2023
|
-
|
|
2024
|
-
```typescript
|
|
2025
|
-
import { usePlugin } from 'logixia';
|
|
2026
|
-
|
|
2027
|
-
usePlugin(myPlugin);
|
|
2028
|
-
|
|
2029
|
-
// All loggers created from this point forward will run myPlugin.
|
|
2030
|
-
const logger = createLogger({/* ... */});
|
|
2031
|
-
```
|
|
2032
|
-
|
|
2033
|
-
### Per-logger plugins
|
|
2034
|
-
|
|
2035
|
-
Register or remove plugins on a specific logger instance at any time:
|
|
2036
|
-
|
|
2037
|
-
```typescript
|
|
2038
|
-
const logger = createLogger({ context: 'PaymentService' });
|
|
2039
|
-
|
|
2040
|
-
// Register
|
|
2041
|
-
logger.use(myPlugin);
|
|
2042
|
-
|
|
2043
|
-
// Deregister by name
|
|
2044
|
-
logger.unuse('my-plugin');
|
|
2045
|
-
```
|
|
2046
|
-
|
|
2047
|
-
`use()` is chainable:
|
|
2048
|
-
|
|
2049
|
-
```typescript
|
|
2050
|
-
createLogger({ context: 'api' }).use(metricsPlugin).use(auditPlugin).use(samplerPlugin);
|
|
2051
|
-
```
|
|
2052
|
-
|
|
2053
|
-
### Cancelling a log entry
|
|
2054
|
-
|
|
2055
|
-
Returning `null` from `onLog` drops the entry before it reaches any transport — useful for sampling, deduplication, or environment-based suppression:
|
|
2056
|
-
|
|
2057
|
-
```typescript
|
|
2058
|
-
const devOnlyPlugin: LogixiaPlugin = {
|
|
2059
|
-
name: 'dev-only',
|
|
2060
|
-
onLog(entry) {
|
|
2061
|
-
// Suppress debug/trace entries in production
|
|
2062
|
-
if (process.env.NODE_ENV === 'production' && entry.level <= 20) {
|
|
2063
|
-
return null; // drop it
|
|
2064
|
-
}
|
|
2065
|
-
return entry;
|
|
2066
|
-
},
|
|
2067
|
-
};
|
|
2068
|
-
```
|
|
2069
|
-
|
|
2070
|
-
Multiple `onLog` hooks run in registration order. If any hook returns `null` the pipeline stops and no further hooks are called.
|
|
2071
|
-
|
|
2072
|
-
---
|
|
2073
|
-
|
|
2074
|
-
## Metrics → Prometheus
|
|
2075
|
-
|
|
2076
|
-
`MetricsPlugin` converts log events into Prometheus-compatible counters, histograms, and gauges — no separate instrumentation library required. The `/metrics` endpoint serves the standard Prometheus text exposition format.
|
|
2077
|
-
|
|
2078
|
-
```typescript
|
|
2079
|
-
import { createMetricsPlugin } from 'logixia';
|
|
2080
|
-
```
|
|
2081
|
-
|
|
2082
|
-
### Quick start (counters)
|
|
2083
|
-
|
|
2084
|
-
```typescript
|
|
2085
|
-
import { createLogger, createMetricsPlugin } from 'logixia';
|
|
2086
|
-
import http from 'node:http';
|
|
2087
|
-
|
|
2088
|
-
const metrics = createMetricsPlugin({
|
|
2089
|
-
http_requests_total: {
|
|
2090
|
-
type: 'counter',
|
|
2091
|
-
help: 'Total HTTP requests processed',
|
|
2092
|
-
// Which log fields become Prometheus labels
|
|
2093
|
-
labels: ['method', 'status', 'route'],
|
|
2094
|
-
},
|
|
2095
|
-
auth_failures_total: {
|
|
2096
|
-
type: 'counter',
|
|
2097
|
-
help: 'Authentication failures',
|
|
2098
|
-
labels: ['reason'],
|
|
2099
|
-
},
|
|
2100
|
-
});
|
|
2101
|
-
|
|
2102
|
-
const logger = createLogger({ context: 'api' }).use(metrics);
|
|
2103
|
-
|
|
2104
|
-
// Every log entry automatically increments the matching counter
|
|
2105
|
-
await logger.info('request handled', { method: 'GET', status: 200, route: '/users' });
|
|
2106
|
-
await logger.warn('auth failed', { reason: 'bad-token' });
|
|
2107
|
-
|
|
2108
|
-
// Expose /metrics
|
|
2109
|
-
http.createServer(metrics.httpHandler()).listen(9100);
|
|
2110
|
-
```
|
|
2111
|
-
|
|
2112
|
-
The `/metrics` response looks like:
|
|
2113
|
-
|
|
2114
|
-
```
|
|
2115
|
-
# HELP logixia_http_requests_total Total HTTP requests processed
|
|
2116
|
-
# TYPE logixia_http_requests_total counter
|
|
2117
|
-
logixia_http_requests_total{method="GET",status="200",route="/users"} 1
|
|
2118
|
-
|
|
2119
|
-
# HELP logixia_auth_failures_total Authentication failures
|
|
2120
|
-
# TYPE logixia_auth_failures_total counter
|
|
2121
|
-
logixia_auth_failures_total{reason="bad-token"} 1
|
|
2122
|
-
```
|
|
2123
|
-
|
|
2124
|
-
### Histograms
|
|
2125
|
-
|
|
2126
|
-
Histograms record the distribution of a numeric field extracted from log entries. Typical use: request latency in milliseconds.
|
|
2127
|
-
|
|
2128
|
-
```typescript
|
|
2129
|
-
const metrics = createMetricsPlugin({
|
|
2130
|
-
http_request_duration_ms: {
|
|
2131
|
-
type: 'histogram',
|
|
2132
|
-
help: 'HTTP request latency in milliseconds',
|
|
2133
|
-
// The log field whose numeric value is recorded as the observation
|
|
2134
|
-
valueField: 'durationMs',
|
|
2135
|
-
labels: ['route', 'method'],
|
|
2136
|
-
// Custom bucket boundaries (defaults: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000])
|
|
2137
|
-
buckets: [10, 50, 100, 200, 500, 1000, 5000],
|
|
2138
|
-
},
|
|
2139
|
-
});
|
|
2140
|
-
|
|
2141
|
-
await logger.info('request complete', { route: '/api/orders', method: 'POST', durationMs: 142 });
|
|
2142
|
-
```
|
|
2143
|
-
|
|
2144
|
-
Prometheus output includes `_bucket`, `_sum`, and `_count` lines, compatible with `histogram_quantile()`:
|
|
2145
|
-
|
|
2146
|
-
```
|
|
2147
|
-
# HELP logixia_http_request_duration_ms HTTP request latency in milliseconds
|
|
2148
|
-
# TYPE logixia_http_request_duration_ms histogram
|
|
2149
|
-
logixia_http_request_duration_ms_bucket{le="10",route="/api/orders",method="POST"} 0
|
|
2150
|
-
logixia_http_request_duration_ms_bucket{le="50",route="/api/orders",method="POST"} 0
|
|
2151
|
-
logixia_http_request_duration_ms_bucket{le="100",route="/api/orders",method="POST"} 0
|
|
2152
|
-
logixia_http_request_duration_ms_bucket{le="200",route="/api/orders",method="POST"} 1
|
|
2153
|
-
...
|
|
2154
|
-
logixia_http_request_duration_ms_bucket{le="+Inf",route="/api/orders",method="POST"} 1
|
|
2155
|
-
logixia_http_request_duration_ms_sum{route="/api/orders",method="POST"} 142
|
|
2156
|
-
logixia_http_request_duration_ms_count{route="/api/orders",method="POST"} 1
|
|
2157
|
-
```
|
|
2158
|
-
|
|
2159
|
-
### Gauges
|
|
2160
|
-
|
|
2161
|
-
Gauges track the current value of a numeric field — useful for queue depths, active connections, cache sizes:
|
|
2162
|
-
|
|
2163
|
-
```typescript
|
|
2164
|
-
const metrics = createMetricsPlugin({
|
|
2165
|
-
queue_depth: {
|
|
2166
|
-
type: 'gauge',
|
|
2167
|
-
help: 'Current number of items in the processing queue',
|
|
2168
|
-
valueField: 'depth',
|
|
2169
|
-
labels: ['queue'],
|
|
2170
|
-
},
|
|
2171
|
-
});
|
|
2172
|
-
|
|
2173
|
-
await logger.info('queue snapshot', { queue: 'email', depth: 47 });
|
|
2174
|
-
```
|
|
2175
|
-
|
|
2176
|
-
### Exposing the /metrics endpoint
|
|
2177
|
-
|
|
2178
|
-
**Plain Node.js `http` module:**
|
|
2179
|
-
|
|
2180
|
-
```typescript
|
|
2181
|
-
import http from 'node:http';
|
|
2182
|
-
|
|
2183
|
-
http.createServer(metrics.httpHandler()).listen(9100);
|
|
2184
|
-
// GET http://localhost:9100/ → Prometheus text format
|
|
2185
|
-
```
|
|
2186
|
-
|
|
2187
|
-
**Express:**
|
|
2188
|
-
|
|
2189
|
-
```typescript
|
|
2190
|
-
import express from 'express';
|
|
2191
|
-
|
|
2192
|
-
const app = express();
|
|
2193
|
-
app.get('/metrics', metrics.expressHandler());
|
|
2194
|
-
app.listen(3000);
|
|
2195
|
-
```
|
|
2196
|
-
|
|
2197
|
-
**Manual render (any framework):**
|
|
2198
|
-
|
|
2199
|
-
```typescript
|
|
2200
|
-
// Returns the full Prometheus text string
|
|
2201
|
-
const text = metrics.render();
|
|
2202
|
-
res.setHeader('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
|
|
2203
|
-
res.end(text);
|
|
2204
|
-
```
|
|
2205
|
-
|
|
2206
|
-
**Reset all counters** (e.g., between tests):
|
|
2207
|
-
|
|
2208
|
-
```typescript
|
|
2209
|
-
metrics.reset();
|
|
2210
|
-
```
|
|
2211
|
-
|
|
2212
|
-
### Metric configuration reference
|
|
2213
|
-
|
|
2214
|
-
```typescript
|
|
2215
|
-
interface CounterConfig {
|
|
2216
|
-
type: 'counter';
|
|
2217
|
-
help?: string; // # HELP line in Prometheus output
|
|
2218
|
-
labels?: string[]; // Log entry fields to use as label keys
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
interface HistogramConfig {
|
|
2222
|
-
type: 'histogram';
|
|
2223
|
-
help?: string;
|
|
2224
|
-
valueField: string; // The log field holding the numeric observation
|
|
2225
|
-
labels?: string[];
|
|
2226
|
-
buckets?: number[]; // Upper-inclusive bucket boundaries (ms or any unit)
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
interface GaugeConfig {
|
|
2230
|
-
type: 'gauge';
|
|
2231
|
-
help?: string;
|
|
2232
|
-
valueField: string; // The log field whose value sets the gauge
|
|
2233
|
-
labels?: string[];
|
|
2234
|
-
}
|
|
2235
|
-
|
|
2236
|
-
// Map of Prometheus metric name → config
|
|
2237
|
-
type MetricsMap = Record<string, CounterConfig | HistogramConfig | GaugeConfig>;
|
|
2238
|
-
```
|
|
2239
|
-
|
|
2240
|
-
All metric names are automatically prefixed with `logixia_` in the output. If a histogram or gauge entry is missing the `valueField`, the entry is still counted but no numeric observation is recorded.
|
|
2241
|
-
|
|
2242
|
-
---
|
|
2243
|
-
|
|
2244
|
-
## Logger instance API
|
|
2245
|
-
|
|
2246
|
-
Complete reference for every method available on a logger instance returned by `createLogger` or `LogixiaLoggerService`:
|
|
2247
|
-
|
|
2248
|
-
```typescript
|
|
2249
|
-
// Log methods
|
|
2250
|
-
await logger.error(message: string | Error, data?: Record<string, unknown>): Promise<void>
|
|
2251
|
-
await logger.warn(message: string, data?: Record<string, unknown>): Promise<void>
|
|
2252
|
-
await logger.info(message: string, data?: Record<string, unknown>): Promise<void>
|
|
2253
|
-
await logger.debug(message: string, data?: Record<string, unknown>): Promise<void>
|
|
2254
|
-
await logger.trace(message: string, data?: Record<string, unknown>): Promise<void>
|
|
2255
|
-
await logger.verbose(message: string, data?: Record<string, unknown>): Promise<void>
|
|
2256
|
-
await logger.logLevel(level: string, message: string, data?): Promise<void> // dynamic dispatch
|
|
2257
|
-
|
|
2258
|
-
// Timer API
|
|
2259
|
-
logger.time(label: string): void
|
|
2260
|
-
await logger.timeEnd(label: string): Promise<number | undefined> // returns ms
|
|
2261
|
-
await logger.timeAsync<T>(label: string, fn: () => Promise<T>): Promise<T>
|
|
2262
|
-
|
|
2263
|
-
// Level management
|
|
2264
|
-
logger.setLevel(level: string): void
|
|
2265
|
-
logger.getLevel(): string
|
|
2266
|
-
|
|
2267
|
-
// Context management
|
|
2268
|
-
logger.setContext(context: string): void
|
|
2269
|
-
logger.getContext(): string | undefined
|
|
2270
|
-
logger.child(context: string, data?: Record<string, unknown>): ILogger
|
|
2271
|
-
|
|
2272
|
-
// Field management
|
|
2273
|
-
logger.enableField(fieldName: string): void
|
|
2274
|
-
logger.disableField(fieldName: string): void
|
|
2275
|
-
logger.isFieldEnabled(fieldName: string): boolean
|
|
2276
|
-
logger.getFieldState(): Record<string, boolean>
|
|
2277
|
-
logger.resetFieldState(): void
|
|
2278
|
-
|
|
2279
|
-
// Transport management
|
|
2280
|
-
logger.getAvailableTransports(): string[]
|
|
2281
|
-
logger.setTransportLevels(transportId: string, levels: string[]): void
|
|
2282
|
-
logger.getTransportLevels(transportId: string): string[] | undefined
|
|
2283
|
-
logger.clearTransportLevelPreferences(): void
|
|
2284
|
-
|
|
2285
|
-
// Plugin API
|
|
2286
|
-
logger.use(plugin: LogixiaPlugin): this // register a plugin; chainable
|
|
2287
|
-
logger.unuse(pluginName: string): this // remove a plugin by name; chainable
|
|
2288
|
-
|
|
2289
|
-
// Lifecycle
|
|
2290
|
-
await logger.flush(): Promise<void>
|
|
2291
|
-
await logger.close(): Promise<void>
|
|
2292
|
-
await logger.healthCheck(): Promise<{ healthy: boolean; details: Record<string, unknown> }>
|
|
2293
|
-
```
|
|
2294
|
-
|
|
2295
|
-
**Utility exports** available at the top level:
|
|
2296
|
-
|
|
2297
|
-
```typescript
|
|
2298
|
-
import {
|
|
2299
|
-
generateTraceId, // () => string — UUID v4
|
|
2300
|
-
getCurrentTraceId, // () => string | undefined
|
|
2301
|
-
runWithTraceId, // (id, fn, data?) => T
|
|
2302
|
-
setTraceId, // (id, data?) => void
|
|
2303
|
-
extractTraceId, // (req, config) => string | undefined
|
|
2304
|
-
isError, // (value) => value is Error
|
|
2305
|
-
normalizeError, // (value) => Error
|
|
2306
|
-
serializeError, // (error, options?) => Record<string, unknown>
|
|
2307
|
-
applyRedaction, // (payload, config) => payload
|
|
2308
|
-
flushOnExit, // (options?) => void
|
|
2309
|
-
registerForShutdown, // (logger) => void
|
|
2310
|
-
deregisterFromShutdown, // (logger) => void
|
|
2311
|
-
resetShutdownHandlers, // () => void — useful in tests
|
|
2312
|
-
} from 'logixia';
|
|
2313
|
-
|
|
2314
|
-
// NestJS extras (requires @nestjs/common):
|
|
2315
|
-
import {
|
|
2316
|
-
InjectLogger, // @InjectLogger() parameter decorator
|
|
2317
|
-
LogMethod, // @LogMethod() method decorator
|
|
2318
|
-
LogixiaExceptionFilter, // global exception filter
|
|
2319
|
-
} from 'logixia/nest';
|
|
2320
|
-
|
|
2321
|
-
// Cloud transports (subpath imports):
|
|
2322
|
-
import { CloudWatchTransport } from 'logixia/cloudwatch';
|
|
2323
|
-
import { GCPTransport } from 'logixia/gcp';
|
|
2324
|
-
import { AzureMonitorTransport } from 'logixia/azure';
|
|
2325
|
-
|
|
2326
|
-
// Plugin API:
|
|
2327
|
-
import type { LogixiaPlugin } from 'logixia';
|
|
2328
|
-
import { globalPluginRegistry, PluginRegistry, usePlugin } from 'logixia';
|
|
2329
|
-
|
|
2330
|
-
// Metrics → Prometheus:
|
|
2331
|
-
import type {
|
|
2332
|
-
CounterConfig,
|
|
2333
|
-
GaugeConfig,
|
|
2334
|
-
HistogramConfig,
|
|
2335
|
-
MetricConfig,
|
|
2336
|
-
MetricsMap,
|
|
2337
|
-
} from 'logixia';
|
|
2338
|
-
import { createMetricsPlugin, MetricsPlugin } from 'logixia';
|
|
2339
|
-
|
|
2340
|
-
// Correlation ID sub-package:
|
|
2341
|
-
import { correlationMiddleware, correlationFetch, withCorrelationId } from 'logixia/correlation';
|
|
2342
|
-
|
|
2343
|
-
// Browser / Edge sub-package:
|
|
2344
|
-
import {
|
|
2345
|
-
createBrowserLogger,
|
|
2346
|
-
BrowserConsoleTransport,
|
|
2347
|
-
BrowserRemoteTransport,
|
|
2348
|
-
} from 'logixia/browser';
|
|
2349
|
-
```
|
|
2350
|
-
|
|
2351
|
-
---
|
|
2352
|
-
|
|
2353
|
-
## CLI tool
|
|
2354
|
-
|
|
2355
|
-
logixia ships a CLI for working with log files directly. After installing, the `logixia` command is available via `npx` or globally:
|
|
2356
|
-
|
|
2357
|
-
```bash
|
|
2358
|
-
npx logixia --help
|
|
2359
|
-
```
|
|
2360
|
-
|
|
2361
|
-
Seven subcommands are available:
|
|
2362
|
-
|
|
2363
|
-
| Command | One-line summary |
|
|
2364
|
-
| --------- | ------------------------------------------------------------ |
|
|
2365
|
-
| `tail` | Stream a log file in real-time with level highlighting |
|
|
2366
|
-
| `search` | Full-text or field-specific search with table / JSON output |
|
|
2367
|
-
| `stats` | Level counts and time distribution summary |
|
|
2368
|
-
| `analyze` | Pattern recognition and anomaly detection |
|
|
2369
|
-
| `export` | Convert between NDJSON, JSON, and CSV |
|
|
2370
|
-
| `query` | SQL-like queries with aggregations, sorting, and live follow |
|
|
2371
|
-
| `explore` | Full-screen interactive TUI browser with search and export |
|
|
2372
|
-
|
|
2373
|
-
**`tail`** — stream a log file in real-time, with optional filtering and level highlighting:
|
|
2374
|
-
|
|
2375
|
-
```bash
|
|
2376
|
-
# Show last 10 lines
|
|
2377
|
-
npx logixia tail ./logs/app.log
|
|
2378
|
-
|
|
2379
|
-
# Follow and filter by level
|
|
2380
|
-
npx logixia tail ./logs/app.log --follow --filter level:error
|
|
2381
|
-
|
|
2382
|
-
# Filter by a specific field value
|
|
2383
|
-
npx logixia tail ./logs/app.log --follow --filter user_id:usr_123
|
|
2384
|
-
|
|
2385
|
-
# Color output by log level
|
|
2386
|
-
npx logixia tail ./logs/app.log --highlight level
|
|
2387
|
-
```
|
|
2388
|
-
|
|
2389
|
-
**`search`** — query a log file with field-specific or full-text search:
|
|
2390
|
-
|
|
2391
|
-
```bash
|
|
2392
|
-
# Full-text search
|
|
2393
|
-
npx logixia search ./logs/app.log --query "payment failed"
|
|
2394
|
-
|
|
2395
|
-
# Field-specific search
|
|
2396
|
-
npx logixia search ./logs/app.log --query "level:error"
|
|
2397
|
-
npx logixia search ./logs/app.log --query "user_id:usr_123"
|
|
2398
|
-
|
|
2399
|
-
# Output as JSON or table
|
|
2400
|
-
npx logixia search ./logs/app.log --query "timeout" --format json
|
|
2401
|
-
npx logixia search ./logs/app.log --query "timeout" --format table
|
|
2402
|
-
```
|
|
2403
|
-
|
|
2404
|
-
**`stats`** — summarize a log file with counts by level and time distribution:
|
|
2405
|
-
|
|
2406
|
-
```bash
|
|
2407
|
-
npx logixia stats ./logs/app.log
|
|
2408
|
-
```
|
|
2409
|
-
|
|
2410
|
-
**`analyze`** — run pattern recognition and anomaly detection across a log file:
|
|
2411
|
-
|
|
2412
|
-
```bash
|
|
2413
|
-
npx logixia analyze ./logs/app.log
|
|
2414
|
-
```
|
|
2415
|
-
|
|
2416
|
-
**`export`** — convert a log file between formats (JSON, CSV, text):
|
|
2417
|
-
|
|
2418
|
-
```bash
|
|
2419
|
-
npx logixia export ./logs/app.log --format csv --output ./logs/app.csv
|
|
2420
|
-
```
|
|
2421
|
-
|
|
2422
|
-
**`query`** — run SQL-like queries over NDJSON / JSON log files directly in your terminal:
|
|
2423
|
-
|
|
2424
|
-
```bash
|
|
2425
|
-
# Basic filter
|
|
2426
|
-
npx logixia query ./logs/app.log --sql "SELECT * FROM logs WHERE level='error'"
|
|
2427
|
-
|
|
2428
|
-
# Multiple conditions
|
|
2429
|
-
npx logixia query ./logs/app.log --sql "WHERE level='error' AND duration > 500"
|
|
2430
|
-
|
|
2431
|
-
# Select specific fields
|
|
2432
|
-
npx logixia query ./logs/app.log --sql "SELECT level, message, duration FROM logs WHERE level='warn'"
|
|
2433
|
-
|
|
2434
|
-
# Time-range shortcuts
|
|
2435
|
-
npx logixia query ./logs/app.log --since "last 2 hours"
|
|
2436
|
-
npx logixia query ./logs/app.log --since "last 30 minutes" --until "last 5 minutes"
|
|
2437
|
-
npx logixia query ./logs/app.log --since today
|
|
2438
|
-
npx logixia query ./logs/app.log --since yesterday
|
|
2439
|
-
|
|
2440
|
-
# Aggregations
|
|
2441
|
-
npx logixia query ./logs/app.log --sql "COUNT BY level"
|
|
2442
|
-
npx logixia query ./logs/app.log --sql "GROUP BY statusCode"
|
|
2443
|
-
npx logixia query ./logs/app.log --sql "AVG(duration) BY endpoint"
|
|
2444
|
-
npx logixia query ./logs/app.log --sql "SUM(duration) BY service"
|
|
2445
|
-
|
|
2446
|
-
# Sorting and limiting
|
|
2447
|
-
npx logixia query ./logs/app.log --sql "WHERE level='error' ORDER BY timestamp DESC LIMIT 20"
|
|
2448
|
-
npx logixia query ./logs/app.log --order-by duration --limit 10
|
|
2449
|
-
|
|
2450
|
-
# Output formats
|
|
2451
|
-
npx logixia query ./logs/app.log --sql "COUNT BY level" --format table
|
|
2452
|
-
npx logixia query ./logs/app.log --sql "WHERE level='error'" --format json
|
|
2453
|
-
|
|
2454
|
-
# Live tail with a SQL filter — streams new entries as they are written
|
|
2455
|
-
npx logixia query ./logs/app.log --follow --sql "WHERE level='error'"
|
|
2456
|
-
npx logixia query ./logs/app.log --follow --since "last 1 hour" --sql "WHERE duration > 1000"
|
|
2457
|
-
```
|
|
2458
|
-
|
|
2459
|
-
Supported SQL features:
|
|
2460
|
-
|
|
2461
|
-
| Clause / keyword | Example |
|
|
2462
|
-
| ---------------------------- | ---------------------------------------- |
|
|
2463
|
-
| `SELECT fields` | `SELECT level, message, duration` |
|
|
2464
|
-
| `WHERE` conditions | `WHERE level='error' AND duration > 500` |
|
|
2465
|
-
| Comparison ops | `= != > >= < <=` |
|
|
2466
|
-
| `LIKE` / `NOT LIKE` | `WHERE message LIKE '%timeout%'` |
|
|
2467
|
-
| `IN` / `NOT IN` | `WHERE level IN ('error', 'warn')` |
|
|
2468
|
-
| `COUNT BY field` | `COUNT BY statusCode` |
|
|
2469
|
-
| `GROUP BY field` | `GROUP BY endpoint` |
|
|
2470
|
-
| `AVG(f) BY g` | `AVG(duration) BY endpoint` |
|
|
2471
|
-
| `SUM / MIN / MAX(f) BY g` | `MAX(duration) BY service` |
|
|
2472
|
-
| `ORDER BY field [ASC\|DESC]` | `ORDER BY timestamp DESC` |
|
|
2473
|
-
| `LIMIT n` | `LIMIT 50` |
|
|
2474
|
-
|
|
2475
|
-
`--since` / `--until` accept: `"last N minutes"`, `"last N hours"`, `"last N days"`, `"today"`, `"yesterday"`, or any ISO 8601 date string.
|
|
2476
|
-
|
|
2477
|
-
### explore — Visual TUI log explorer
|
|
2478
|
-
|
|
2479
|
-
`logixia explore` opens a full-screen interactive terminal UI built on raw ANSI + chalk — no extra runtime dependencies required.
|
|
2480
|
-
|
|
2481
|
-
```bash
|
|
2482
|
-
# Open a log file in the TUI
|
|
2483
|
-
npx logixia explore ./logs/app.log
|
|
2484
|
-
|
|
2485
|
-
# Start with only error and warn entries visible
|
|
2486
|
-
npx logixia explore ./logs/app.log --levels error,warn
|
|
2487
|
-
|
|
2488
|
-
# Pre-populate the search field
|
|
2489
|
-
npx logixia explore ./logs/app.log --search "payment"
|
|
2490
|
-
|
|
2491
|
-
# Follow mode: append new entries as the file grows
|
|
2492
|
-
npx logixia explore ./logs/app.log --follow
|
|
2493
|
-
```
|
|
2494
|
-
|
|
2495
|
-
**Options:**
|
|
2496
|
-
|
|
2497
|
-
| Flag | Default | Description |
|
|
2498
|
-
| ------------------ | ------- | --------------------------------------------------------------------- |
|
|
2499
|
-
| `--follow` | off | Tail the file; append new entries as they are written |
|
|
2500
|
-
| `--levels <list>` | all | Comma-separated levels to show: `error,warn,info,debug,trace,verbose` |
|
|
2501
|
-
| `--search <query>` | — | Pre-populate the search field on open |
|
|
2502
|
-
|
|
2503
|
-
**Layout:**
|
|
2504
|
-
|
|
2505
|
-
```
|
|
2506
|
-
┌────────────────────────────────────────────────────────────────────────────┐
|
|
2507
|
-
│ LOGIXIA EXPLORE app.log [42/127] /payment │
|
|
2508
|
-
│ E W I D T V /: search │
|
|
2509
|
-
│ TIME LVL MESSAGE │
|
|
2510
|
-
│ 08:00:01.042 ERR Request failed status=500 user=abc │
|
|
2511
|
-
│▶08:00:02.117 INF Request completed status=200 user=def ← selected │
|
|
2512
|
-
│ 08:00:03.890 WRN Slow query detected duration=1240 │
|
|
2513
|
-
│──────────────────────────────────────────────────────────────── ▼ DETAIL ─│
|
|
2514
|
-
│ { │
|
|
2515
|
-
│ "timestamp": "...", │
|
|
2516
|
-
│ "level": "info", │
|
|
2517
|
-
│ "message": "Request completed", │
|
|
2518
|
-
│ "status": 200, │
|
|
2519
|
-
│ "user": "def" │
|
|
2520
|
-
│ } │
|
|
2521
|
-
│ j/k move /search x export E/W/I/D/T/V filter J/K detail↕ q quit │
|
|
2522
|
-
└────────────────────────────────────────────────────────────────────────────┘
|
|
2523
|
-
```
|
|
2524
|
-
|
|
2525
|
-
**Keyboard shortcuts:**
|
|
2526
|
-
|
|
2527
|
-
| Key | Action |
|
|
2528
|
-
| --------------- | ------------------------------------------------------------------------------ |
|
|
2529
|
-
| `j` / `↓` | Move selection down |
|
|
2530
|
-
| `k` / `↑` | Move selection up |
|
|
2531
|
-
| `g` / `Home` | Jump to first entry |
|
|
2532
|
-
| `G` / `End` | Jump to last entry |
|
|
2533
|
-
| `PgUp` / `PgDn` | Page up / page down |
|
|
2534
|
-
| `J` / `K` | Scroll detail panel down / up |
|
|
2535
|
-
| `/` | Enter search mode (type query → `Enter` to confirm, `Esc` to clear) |
|
|
2536
|
-
| `E W I D T V` | Toggle error / warn / info / debug / trace / verbose filter |
|
|
2537
|
-
| `f` | Toggle real-time follow mode |
|
|
2538
|
-
| `x` | Export filtered entries (enter a path ending in `.json`, `.csv`, or `.ndjson`) |
|
|
2539
|
-
| `q` / `Ctrl+C` | Quit and return to terminal |
|
|
2540
|
-
|
|
2541
|
-
**Detail panel** shows syntax-highlighted JSON of the selected entry. For error entries with a `stack` field, the full stack trace is rendered below the JSON with frame locations highlighted.
|
|
2542
|
-
|
|
2543
|
-
**Export** supports three formats determined by the file extension you type:
|
|
2544
|
-
|
|
2545
|
-
- `.json` — pretty-printed JSON array
|
|
2546
|
-
- `.csv` — comma-separated with auto-detected column headers
|
|
2547
|
-
- `.ndjson` / `.jsonl` — newline-delimited JSON (one entry per line)
|
|
2548
|
-
|
|
2549
|
-
The explorer works in any TTY-capable terminal (macOS Terminal, iTerm2, Windows Terminal, VS Code integrated terminal) and degrades gracefully in non-TTY environments (useful when piping to other commands).
|
|
2550
|
-
|
|
2551
|
-
---
|
|
2552
|
-
|
|
2553
|
-
## Configuration reference
|
|
2554
|
-
|
|
2555
|
-
```typescript
|
|
2556
|
-
interface LoggerConfig {
|
|
2557
|
-
// Required
|
|
2558
|
-
appName: string;
|
|
2559
|
-
environment: 'development' | 'production';
|
|
2560
|
-
|
|
2561
|
-
// Output format (applies to console and file text output)
|
|
2562
|
-
format?: {
|
|
2563
|
-
timestamp?: boolean; // include ISO timestamp. Default: true
|
|
2564
|
-
colorize?: boolean; // ANSI color output. Default: true
|
|
2565
|
-
json?: boolean; // JSON lines output. Default: false
|
|
2566
|
-
};
|
|
2567
|
-
|
|
2568
|
-
// Trace ID — true enables UUID v4 auto-generation; pass an object for custom config
|
|
2569
|
-
traceId?:
|
|
2570
|
-
| boolean
|
|
2571
|
-
| {
|
|
2572
|
-
enabled: boolean;
|
|
2573
|
-
generator?: () => string; // custom ID generator
|
|
2574
|
-
contextKey?: string;
|
|
2575
|
-
extractor?: {
|
|
2576
|
-
header?: string | string[]; // headers to check
|
|
2577
|
-
query?: string | string[]; // query params to check
|
|
2578
|
-
body?: string | string[]; // body fields to check
|
|
2579
|
-
params?: string | string[]; // route params to check
|
|
2580
|
-
};
|
|
2581
|
-
};
|
|
2582
|
-
|
|
2583
|
-
// Suppress all output. Useful in test environments
|
|
2584
|
-
silent?: boolean;
|
|
2585
|
-
|
|
2586
|
-
// Level configuration
|
|
2587
|
-
levelOptions?: {
|
|
2588
|
-
level?: 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'verbose' | string;
|
|
2589
|
-
levels?: Record<string, number>; // custom level priority map
|
|
2590
|
-
colors?: Record<string, LogColor>; // color per level
|
|
2591
|
-
};
|
|
2592
|
-
|
|
2593
|
-
// Visible fields in text output
|
|
2594
|
-
fields?: Partial<
|
|
2595
|
-
Record<
|
|
2596
|
-
| 'timestamp'
|
|
2597
|
-
| 'level'
|
|
2598
|
-
| 'appName'
|
|
2599
|
-
| 'service'
|
|
2600
|
-
| 'traceId'
|
|
2601
|
-
| 'message'
|
|
2602
|
-
| 'payload'
|
|
2603
|
-
| 'timeTaken'
|
|
2604
|
-
| 'context'
|
|
2605
|
-
| 'requestId'
|
|
2606
|
-
| 'userId'
|
|
2607
|
-
| 'sessionId'
|
|
2608
|
-
| 'environment',
|
|
2609
|
-
string | boolean
|
|
2610
|
-
>
|
|
2611
|
-
>;
|
|
2612
|
-
|
|
2613
|
-
// Field redaction — applied before any transport receives the entry
|
|
2614
|
-
redact?: {
|
|
2615
|
-
paths?: string[]; // dot-notation paths; supports * and ** wildcards
|
|
2616
|
-
patterns?: RegExp[]; // regex patterns applied to string values
|
|
2617
|
-
censor?: string; // replacement string. Default: '[REDACTED]'
|
|
2618
|
-
};
|
|
2619
|
-
|
|
2620
|
-
// Per-namespace log level overrides; keys are patterns, values are levels
|
|
2621
|
-
namespaceLevels?: Record<string, string>;
|
|
2622
|
-
|
|
2623
|
-
// Graceful shutdown — true for defaults, or a config object for full control
|
|
2624
|
-
gracefulShutdown?:
|
|
2625
|
-
| boolean
|
|
2626
|
-
| {
|
|
2627
|
-
enabled: boolean;
|
|
2628
|
-
timeout?: number; // ms to wait before force-exit. Default: 5000
|
|
2629
|
-
signals?: NodeJS.Signals[]; // Default: ['SIGTERM', 'SIGINT']
|
|
2630
|
-
};
|
|
2631
|
-
|
|
2632
|
-
// Transports — all optional, all concurrent
|
|
2633
|
-
transports?: {
|
|
2634
|
-
console?: {
|
|
2635
|
-
level?: string;
|
|
2636
|
-
colorize?: boolean;
|
|
2637
|
-
timestamp?: boolean;
|
|
2638
|
-
format?: 'json' | 'text';
|
|
2639
|
-
filter?: (entry: TransportLogEntry) => boolean;
|
|
2640
|
-
};
|
|
2641
|
-
|
|
2642
|
-
file?:
|
|
2643
|
-
| {
|
|
2644
|
-
filename: string;
|
|
2645
|
-
dirname?: string;
|
|
2646
|
-
maxSize?: string | number; // e.g. '50MB', '1GB', or bytes
|
|
2647
|
-
maxFiles?: number;
|
|
2648
|
-
datePattern?: string; // e.g. 'YYYY-MM-DD'
|
|
2649
|
-
zippedArchive?: boolean;
|
|
2650
|
-
format?: 'json' | 'text' | 'csv';
|
|
2651
|
-
level?: string;
|
|
2652
|
-
batchSize?: number;
|
|
2653
|
-
flushInterval?: number; // ms
|
|
2654
|
-
maxQueue?: number; // cap on unwritten entries held in memory. Default: no cap
|
|
2655
|
-
queuePolicy?: 'drop-oldest' | 'drop-newest' | 'block'; // Default: 'drop-oldest'
|
|
2656
|
-
rotation?: {
|
|
2657
|
-
interval?: '1h' | '6h' | '12h' | '1d' | '1w' | '1m' | '1y';
|
|
2658
|
-
maxSize?: string | number;
|
|
2659
|
-
maxFiles?: number;
|
|
2660
|
-
compress?: boolean;
|
|
2661
|
-
shouldRotate?: (
|
|
2662
|
-
currentFile: string,
|
|
2663
|
-
stats: { size: number; created: Date; modified: Date }
|
|
2664
|
-
) => boolean;
|
|
2665
|
-
};
|
|
2666
|
-
filter?: (entry: TransportLogEntry) => boolean;
|
|
2667
|
-
}
|
|
2668
|
-
| Array<FileTransportConfig>; // array for multiple file targets
|
|
2669
|
-
|
|
2670
|
-
database?:
|
|
2671
|
-
| {
|
|
2672
|
-
type: 'postgresql' | 'mysql' | 'mongodb' | 'sqlite';
|
|
2673
|
-
host?: string;
|
|
2674
|
-
port?: number;
|
|
2675
|
-
database: string;
|
|
2676
|
-
table?: string; // SQL databases
|
|
2677
|
-
collection?: string; // MongoDB
|
|
2678
|
-
connectionString?: string; // MongoDB connection string
|
|
2679
|
-
username?: string;
|
|
2680
|
-
password?: string;
|
|
2681
|
-
ssl?: boolean;
|
|
2682
|
-
level?: string; // minimum level threshold for this transport
|
|
2683
|
-
batchSize?: number;
|
|
2684
|
-
flushInterval?: number; // ms
|
|
2685
|
-
filter?: (entry: TransportLogEntry) => boolean; // entry-level predicate
|
|
2686
|
-
}
|
|
2687
|
-
| Array<DatabaseTransportConfig>;
|
|
2688
|
-
|
|
2689
|
-
analytics?: {
|
|
2690
|
-
datadog?: {
|
|
2691
|
-
apiKey: string;
|
|
2692
|
-
site?: 'datadoghq.com' | 'datadoghq.eu' | 'us3.datadoghq.com' | 'us5.datadoghq.com';
|
|
2693
|
-
service?: string;
|
|
2694
|
-
version?: string;
|
|
2695
|
-
env?: string;
|
|
2696
|
-
enableMetrics?: boolean;
|
|
2697
|
-
enableLogs?: boolean;
|
|
2698
|
-
enableTraces?: boolean;
|
|
2699
|
-
};
|
|
2700
|
-
mixpanel?: {
|
|
2701
|
-
token: string;
|
|
2702
|
-
distinct_id?: string;
|
|
2703
|
-
enableSuperProperties?: boolean;
|
|
2704
|
-
superProperties?: Record<string, unknown>;
|
|
2705
|
-
};
|
|
2706
|
-
segment?: {
|
|
2707
|
-
writeKey: string;
|
|
2708
|
-
dataPlaneUrl?: string;
|
|
2709
|
-
enableBatching?: boolean;
|
|
2710
|
-
flushAt?: number;
|
|
2711
|
-
flushInterval?: number;
|
|
2712
|
-
};
|
|
2713
|
-
googleAnalytics?: {
|
|
2714
|
-
measurementId: string;
|
|
2715
|
-
apiSecret: string;
|
|
2716
|
-
clientId?: string;
|
|
2717
|
-
enableEcommerce?: boolean;
|
|
2718
|
-
};
|
|
2719
|
-
};
|
|
2720
|
-
|
|
2721
|
-
custom?: ITransport[]; // any object implementing { name, write, close? }
|
|
2722
|
-
};
|
|
2723
|
-
}
|
|
2724
|
-
```
|
|
2725
|
-
|
|
2726
|
-
---
|
|
2727
|
-
|
|
2728
|
-
## Sponsor
|
|
2729
|
-
|
|
2730
|
-
logixia is free and MIT-licensed, and it is built and maintained by one person. If it saves you time, sponsoring keeps it moving:
|
|
2731
|
-
|
|
2732
|
-
- [GitHub Sponsors](https://github.com/sponsors/webcoderspeed)
|
|
2733
|
-
- [PayPal](https://paypal.me/Sniperspeed)
|
|
322
|
+
- [GitHub Sponsors](https://github.com/sponsors/webcoderspeed)
|
|
323
|
+
- [PayPal](https://paypal.me/Sniperspeed)
|
|
2734
324
|
|
|
2735
325
|
`npm fund logixia` prints the same links.
|
|
2736
326
|
|
|
2737
|
-
---
|
|
2738
|
-
|
|
2739
|
-
## Contributing
|
|
2740
|
-
|
|
2741
|
-
```bash
|
|
2742
|
-
git clone https://github.com/Logixia/logixia.git
|
|
2743
|
-
cd logixia
|
|
2744
|
-
npm install
|
|
2745
|
-
npm test
|
|
2746
|
-
```
|
|
2747
|
-
|
|
2748
|
-
Pull requests are welcome. For significant changes, please open an issue first to discuss what you'd like to change.
|
|
2749
|
-
|
|
2750
|
-
---
|
|
2751
|
-
|
|
2752
327
|
## License
|
|
2753
328
|
|
|
2754
|
-
[MIT](
|
|
329
|
+
[MIT](./LICENSE) © [Sanjeev Sharma](https://github.com/webcoderspeed)
|