logixia 2.0.4 → 2.0.7

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.
Files changed (218) hide show
  1. package/README.md +172 -2614
  2. package/dist/{async-context-B05ArHgM.mjs → async-context-COuvmwsS.mjs} +4 -6
  3. package/dist/async-context-COuvmwsS.mjs.map +1 -0
  4. package/dist/{async-context-DTQgi8f7.js → async-context-Dv0bmPC-.js} +4 -6
  5. package/dist/async-context-Dv0bmPC-.js.map +1 -0
  6. package/dist/{azure-monitor.transport-DV03Z5P4.d.ts → azure-monitor.transport-Bvf7Xegt.d.ts} +2 -2
  7. package/dist/{azure-monitor.transport-DV03Z5P4.d.ts.map → azure-monitor.transport-Bvf7Xegt.d.ts.map} +1 -1
  8. package/dist/{azure-monitor.transport-C7kYyZUW.d.mts → azure-monitor.transport-CbSHOnNX.d.mts} +2 -2
  9. package/dist/{azure-monitor.transport-C7kYyZUW.d.mts.map → azure-monitor.transport-CbSHOnNX.d.mts.map} +1 -1
  10. package/dist/{azure-monitor.transport-BPbgjATu.mjs → azure-monitor.transport-Coz5Gesu.mjs} +5 -5
  11. package/dist/azure-monitor.transport-Coz5Gesu.mjs.map +1 -0
  12. package/dist/{azure-monitor.transport-CwbOLZHB.js → azure-monitor.transport-D6ryi8rA.js} +5 -5
  13. package/dist/azure-monitor.transport-D6ryi8rA.js.map +1 -0
  14. package/dist/azure.d.mts +1 -1
  15. package/dist/azure.d.ts +1 -1
  16. package/dist/azure.js +1 -1
  17. package/dist/azure.mjs +1 -1
  18. package/dist/{batch-buffer-DT4gdXmf.js → batch-buffer-CFzKnuNZ.js} +3 -3
  19. package/dist/{batch-buffer-DT4gdXmf.js.map → batch-buffer-CFzKnuNZ.js.map} +1 -1
  20. package/dist/{batch-buffer-0fgLmIPn.mjs → batch-buffer-CQUWDMYq.mjs} +3 -3
  21. package/dist/{batch-buffer-0fgLmIPn.mjs.map → batch-buffer-CQUWDMYq.mjs.map} +1 -1
  22. package/dist/browser.d.mts +5 -5
  23. package/dist/browser.d.mts.map +1 -1
  24. package/dist/browser.d.ts +5 -5
  25. package/dist/browser.d.ts.map +1 -1
  26. package/dist/browser.js +72 -13
  27. package/dist/browser.js.map +1 -1
  28. package/dist/browser.mjs +72 -13
  29. package/dist/browser.mjs.map +1 -1
  30. package/dist/{builder-D70z6kjj.mjs → builder-C_TgxEOW.mjs} +106 -153
  31. package/dist/builder-C_TgxEOW.mjs.map +1 -0
  32. package/dist/{builder-M7NoaugM.js → builder-CzmM_4Ub.js} +110 -180
  33. package/dist/builder-CzmM_4Ub.js.map +1 -0
  34. package/dist/cli/index.js +157 -66
  35. package/dist/cloudwatch.d.mts +1 -1
  36. package/dist/cloudwatch.d.ts +1 -1
  37. package/dist/cloudwatch.js +1 -1
  38. package/dist/cloudwatch.mjs +1 -1
  39. package/dist/{cloudwatch.transport-Bf7L4I59.d.ts → cloudwatch.transport-CQyT6U91.d.ts} +2 -2
  40. package/dist/{cloudwatch.transport-Bf7L4I59.d.ts.map → cloudwatch.transport-CQyT6U91.d.ts.map} +1 -1
  41. package/dist/{cloudwatch.transport-Rj4yTpRn.d.mts → cloudwatch.transport-CjVrI6S4.d.mts} +2 -2
  42. package/dist/{cloudwatch.transport-Rj4yTpRn.d.mts.map → cloudwatch.transport-CjVrI6S4.d.mts.map} +1 -1
  43. package/dist/{cloudwatch.transport-BTvYfLs9.mjs → cloudwatch.transport-Cr1aeTGj.mjs} +6 -6
  44. package/dist/cloudwatch.transport-Cr1aeTGj.mjs.map +1 -0
  45. package/dist/{cloudwatch.transport-B2wKQFF7.js → cloudwatch.transport-D4G5xYfG.js} +6 -6
  46. package/dist/cloudwatch.transport-D4G5xYfG.js.map +1 -0
  47. package/dist/correlation.d.mts +10 -55
  48. package/dist/correlation.d.mts.map +1 -1
  49. package/dist/correlation.d.ts +10 -55
  50. package/dist/correlation.d.ts.map +1 -1
  51. package/dist/correlation.js +15 -13
  52. package/dist/correlation.js.map +1 -1
  53. package/dist/correlation.mjs +15 -13
  54. package/dist/correlation.mjs.map +1 -1
  55. package/dist/{entry-fields-RLd1jCoR.js → entry-fields-CVwoQplO.js} +8 -3
  56. package/dist/entry-fields-CVwoQplO.js.map +1 -0
  57. package/dist/{entry-fields-CGuvT2R0.mjs → entry-fields-DNTKQxoU.mjs} +8 -3
  58. package/dist/entry-fields-DNTKQxoU.mjs.map +1 -0
  59. package/dist/gcp.d.mts +1 -1
  60. package/dist/gcp.d.ts +1 -1
  61. package/dist/gcp.js +1 -1
  62. package/dist/gcp.mjs +1 -1
  63. package/dist/{gcp.transport-CFQfdAOK.d.ts → gcp.transport-BCIvdFV-.d.ts} +4 -4
  64. package/dist/{gcp.transport-CFQfdAOK.d.ts.map → gcp.transport-BCIvdFV-.d.ts.map} +1 -1
  65. package/dist/{gcp.transport-KZe9hCAR.js → gcp.transport-CVuRpchl.js} +20 -16
  66. package/dist/gcp.transport-CVuRpchl.js.map +1 -0
  67. package/dist/{gcp.transport-C5D72b1g.d.mts → gcp.transport-D9SdLPwP.d.mts} +4 -4
  68. package/dist/{gcp.transport-C5D72b1g.d.mts.map → gcp.transport-D9SdLPwP.d.mts.map} +1 -1
  69. package/dist/{gcp.transport-CGKopAhQ.mjs → gcp.transport-LLK2bw2-.mjs} +20 -16
  70. package/dist/gcp.transport-LLK2bw2-.mjs.map +1 -0
  71. package/dist/http-shapes-BnBkJcid.d.mts +32 -0
  72. package/dist/http-shapes-BnBkJcid.d.mts.map +1 -0
  73. package/dist/http-shapes-BnBkJcid.d.ts +32 -0
  74. package/dist/http-shapes-BnBkJcid.d.ts.map +1 -0
  75. package/dist/{index-xqaJtAZn.d.mts → index-BAkPHp5D.d.mts} +150 -8
  76. package/dist/index-BAkPHp5D.d.mts.map +1 -0
  77. package/dist/{index-DwozbmB4.d.ts → index-e7LowmAu.d.ts} +150 -8
  78. package/dist/index-e7LowmAu.d.ts.map +1 -0
  79. package/dist/index.d.mts +21 -65
  80. package/dist/index.d.mts.map +1 -1
  81. package/dist/index.d.ts +21 -65
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +26 -26
  84. package/dist/index.js.map +1 -1
  85. package/dist/index.mjs +21 -21
  86. package/dist/index.mjs.map +1 -1
  87. package/dist/{internal-log-uCSd1PMc.mjs → internal-log-Cy5nF1ZL.mjs} +2 -81
  88. package/dist/internal-log-Cy5nF1ZL.mjs.map +1 -0
  89. package/dist/{internal-log-b0Vp4qrB.js → internal-log-DwqTjhpb.js} +1 -104
  90. package/dist/internal-log-DwqTjhpb.js.map +1 -0
  91. package/dist/{logitron-logger-D2B75Oc7.d.ts → logitron-logger-Qh3y9-WH.d.ts} +13 -100
  92. package/dist/logitron-logger-Qh3y9-WH.d.ts.map +1 -0
  93. package/dist/{logitron-logger-oEd6wNhf.d.mts → logitron-logger-sYuk8fOb.d.mts} +13 -100
  94. package/dist/logitron-logger-sYuk8fOb.d.mts.map +1 -0
  95. package/dist/middleware.d.mts +4 -17
  96. package/dist/middleware.d.mts.map +1 -1
  97. package/dist/middleware.d.ts +4 -17
  98. package/dist/middleware.d.ts.map +1 -1
  99. package/dist/middleware.js +9 -39
  100. package/dist/middleware.js.map +1 -1
  101. package/dist/middleware.mjs +7 -37
  102. package/dist/middleware.mjs.map +1 -1
  103. package/dist/nest.d.mts +24 -8
  104. package/dist/nest.d.mts.map +1 -1
  105. package/dist/nest.d.ts +24 -8
  106. package/dist/nest.d.ts.map +1 -1
  107. package/dist/nest.js +140 -27
  108. package/dist/nest.js.map +1 -1
  109. package/dist/nest.mjs +140 -27
  110. package/dist/nest.mjs.map +1 -1
  111. package/dist/{transport.manager-DkcEOlqV.js → otel-ChnVYRLA.js} +214 -23
  112. package/dist/otel-ChnVYRLA.js.map +1 -0
  113. package/dist/{transport.manager-eZ_EeC00.mjs → otel-DOuwGPCd.mjs} +173 -24
  114. package/dist/otel-DOuwGPCd.mjs.map +1 -0
  115. package/dist/{request-trace-C_fvIDv4.mjs → request-trace-BgNykH9M.mjs} +1 -5
  116. package/dist/{request-trace-C_fvIDv4.mjs.map → request-trace-BgNykH9M.mjs.map} +1 -1
  117. package/dist/{request-trace-wTJdKp6-.js → request-trace-ByujsUZk.js} +1 -5
  118. package/dist/{request-trace-wTJdKp6-.js.map → request-trace-ByujsUZk.js.map} +1 -1
  119. package/dist/{safe-stringify-BMRl3EX3.mjs → safe-stringify--7gbe95n.mjs} +37 -12
  120. package/dist/safe-stringify--7gbe95n.mjs.map +1 -0
  121. package/dist/{safe-stringify-DLz1Kz3o.js → safe-stringify-CN7icp8S.js} +37 -12
  122. package/dist/safe-stringify-CN7icp8S.js.map +1 -0
  123. package/dist/{search-DUfWuzFP.js → search-CRjhHxe9.js} +269 -25
  124. package/dist/search-CRjhHxe9.js.map +1 -0
  125. package/dist/{search-CazwyX5Z.d.ts → search-D-8HJADE.d.mts} +5 -2
  126. package/dist/search-D-8HJADE.d.mts.map +1 -0
  127. package/dist/{search-CWMVYC-R.mjs → search-DAXKE-P9.mjs} +269 -25
  128. package/dist/search-DAXKE-P9.mjs.map +1 -0
  129. package/dist/{search-BH33XhuU.d.mts → search-DPwTKPve.d.ts} +5 -2
  130. package/dist/search-DPwTKPve.d.ts.map +1 -0
  131. package/dist/search.d.mts +1 -1
  132. package/dist/search.d.ts +1 -1
  133. package/dist/search.js +1 -1
  134. package/dist/search.mjs +1 -1
  135. package/dist/{secret-config-CBZBs0p1.js → secret-config-BvFl2dC8.js} +7 -7
  136. package/dist/secret-config-BvFl2dC8.js.map +1 -0
  137. package/dist/{secret-config-BegMLkYl.mjs → secret-config-ByGq7kJd.mjs} +5 -5
  138. package/dist/secret-config-ByGq7kJd.mjs.map +1 -0
  139. package/dist/sensitive-names-CHMowujD.js +276 -0
  140. package/dist/sensitive-names-CHMowujD.js.map +1 -0
  141. package/dist/sensitive-names-o__1PUUR.mjs +217 -0
  142. package/dist/sensitive-names-o__1PUUR.mjs.map +1 -0
  143. package/dist/{slack.transport-DxHOXlLu.d.mts → slack.transport-BDyENuR0.d.mts} +2 -2
  144. package/dist/{slack.transport-DxHOXlLu.d.mts.map → slack.transport-BDyENuR0.d.mts.map} +1 -1
  145. package/dist/{slack.transport-DrirgDNy.d.ts → slack.transport-C6E0l4kw.d.ts} +2 -2
  146. package/dist/{slack.transport-DrirgDNy.d.ts.map → slack.transport-C6E0l4kw.d.ts.map} +1 -1
  147. package/dist/{slack.transport-Ce96JBDk.mjs → slack.transport-CSj1j0_X.mjs} +66 -15
  148. package/dist/slack.transport-CSj1j0_X.mjs.map +1 -0
  149. package/dist/{slack.transport-Dlx3xvb7.js → slack.transport-zLQ9MGxS.js} +67 -16
  150. package/dist/slack.transport-zLQ9MGxS.js.map +1 -0
  151. package/dist/testing.d.mts +11 -4
  152. package/dist/testing.d.mts.map +1 -1
  153. package/dist/testing.d.ts +11 -4
  154. package/dist/testing.d.ts.map +1 -1
  155. package/dist/testing.js +38 -3
  156. package/dist/testing.js.map +1 -1
  157. package/dist/testing.mjs +38 -3
  158. package/dist/testing.mjs.map +1 -1
  159. package/dist/{transport.types-BJVL1QQl.d.ts → transport.types-CzJq4IrF.d.mts} +14 -3
  160. package/dist/transport.types-CzJq4IrF.d.mts.map +1 -0
  161. package/dist/{transport.types-BJVL1QQl.d.mts → transport.types-CzJq4IrF.d.ts} +14 -3
  162. package/dist/transport.types-CzJq4IrF.d.ts.map +1 -0
  163. package/dist/transports.d.mts +25 -13
  164. package/dist/transports.d.mts.map +1 -1
  165. package/dist/transports.d.ts +25 -13
  166. package/dist/transports.d.ts.map +1 -1
  167. package/dist/transports.js +15 -15
  168. package/dist/transports.mjs +6 -6
  169. package/dist/worker-entry.js +130 -25
  170. package/dist/worker.d.mts +1 -1
  171. package/dist/worker.d.ts +1 -1
  172. package/dist/worker.js +1 -1
  173. package/dist/worker.mjs +1 -1
  174. package/dist/{worker.transport-BuPwBi3d.d.ts → worker.transport-B9mCQSM1.d.ts} +7 -2
  175. package/dist/worker.transport-B9mCQSM1.d.ts.map +1 -0
  176. package/dist/{worker.transport-hJay1wxA.d.mts → worker.transport-Bl8lFRck.d.mts} +7 -2
  177. package/dist/worker.transport-Bl8lFRck.d.mts.map +1 -0
  178. package/dist/{worker.transport-DLVA4y2e.mjs → worker.transport-D78xhNhB.mjs} +13 -10
  179. package/dist/worker.transport-D78xhNhB.mjs.map +1 -0
  180. package/dist/{worker.transport-C6GNl_r2.js → worker.transport-DQDv1nAJ.js} +13 -10
  181. package/dist/worker.transport-DQDv1nAJ.js.map +1 -0
  182. package/package.json +33 -65
  183. package/dist/async-context-B05ArHgM.mjs.map +0 -1
  184. package/dist/async-context-DTQgi8f7.js.map +0 -1
  185. package/dist/azure-monitor.transport-BPbgjATu.mjs.map +0 -1
  186. package/dist/azure-monitor.transport-CwbOLZHB.js.map +0 -1
  187. package/dist/builder-D70z6kjj.mjs.map +0 -1
  188. package/dist/builder-M7NoaugM.js.map +0 -1
  189. package/dist/cloudwatch.transport-B2wKQFF7.js.map +0 -1
  190. package/dist/cloudwatch.transport-BTvYfLs9.mjs.map +0 -1
  191. package/dist/entry-fields-CGuvT2R0.mjs.map +0 -1
  192. package/dist/entry-fields-RLd1jCoR.js.map +0 -1
  193. package/dist/gcp.transport-CGKopAhQ.mjs.map +0 -1
  194. package/dist/gcp.transport-KZe9hCAR.js.map +0 -1
  195. package/dist/index-DwozbmB4.d.ts.map +0 -1
  196. package/dist/index-xqaJtAZn.d.mts.map +0 -1
  197. package/dist/internal-log-b0Vp4qrB.js.map +0 -1
  198. package/dist/internal-log-uCSd1PMc.mjs.map +0 -1
  199. package/dist/logitron-logger-D2B75Oc7.d.ts.map +0 -1
  200. package/dist/logitron-logger-oEd6wNhf.d.mts.map +0 -1
  201. package/dist/safe-stringify-BMRl3EX3.mjs.map +0 -1
  202. package/dist/safe-stringify-DLz1Kz3o.js.map +0 -1
  203. package/dist/search-BH33XhuU.d.mts.map +0 -1
  204. package/dist/search-CWMVYC-R.mjs.map +0 -1
  205. package/dist/search-CazwyX5Z.d.ts.map +0 -1
  206. package/dist/search-DUfWuzFP.js.map +0 -1
  207. package/dist/secret-config-BegMLkYl.mjs.map +0 -1
  208. package/dist/secret-config-CBZBs0p1.js.map +0 -1
  209. package/dist/slack.transport-Ce96JBDk.mjs.map +0 -1
  210. package/dist/slack.transport-Dlx3xvb7.js.map +0 -1
  211. package/dist/transport.manager-DkcEOlqV.js.map +0 -1
  212. package/dist/transport.manager-eZ_EeC00.mjs.map +0 -1
  213. package/dist/transport.types-BJVL1QQl.d.mts.map +0 -1
  214. package/dist/transport.types-BJVL1QQl.d.ts.map +0 -1
  215. package/dist/worker.transport-BuPwBi3d.d.ts.map +0 -1
  216. package/dist/worker.transport-C6GNl_r2.js.map +0 -1
  217. package/dist/worker.transport-DLVA4y2e.mjs.map +0 -1
  218. package/dist/worker.transport-hJay1wxA.d.mts.map +0 -1
package/README.md CHANGED
@@ -1,2771 +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. One install, every transport, never blocks the event loop." width="100%"/>
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>The async-first TypeScript logger that ships complete.</strong><br/>
9
- Wide events &middot; OpenTelemetry (OTLP) &middot; Runtime log levels &middot; Redaction &middot; Adaptive sampling<br/>
10
- NestJS &middot; Express &middot; Fastify &middot; Database &middot; Cloud &middot; Tracing &middot; Prometheus &middot; 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://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT"/></a>
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>Website</strong></a> &middot;
26
- <a href="https://github.com/Logixia/logixia">GitHub</a> &middot;
27
- <a href="https://www.npmjs.com/package/logixia">npm</a> &middot;
21
+ <a href="https://logixia.github.io/logixia/"><strong>Documentation</strong></a> &middot;
22
+ <a href="https://logixia.github.io/logixia/guide/getting-started">Getting started</a> &middot;
23
+ <a href="https://logixia.github.io/logixia/guide/migration-v2">Upgrading to 2.x</a> &middot;
28
24
  <a href="https://github.com/Logixia/logixia/issues">Issues</a>
29
25
  </p>
30
26
 
31
27
  ---
32
28
 
33
- ## The logging setup you copy-paste into every new project
29
+ ## Why logixia
34
30
 
35
- ```bash
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
- **What this means:**
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
- - **pino is the fastest raw JSON serializer here.** It leads in 5 of 6 scenarios, by 21–47%. If raw throughput is the only thing you need, pino is a great choice.
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
- **Distinctive-feature throughput** (no cross-library equivalent — `npm run benchmark:features`):
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
- **Requirements:** TypeScript 5.0+, Node.js 18+
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: 'production',
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
- Without a `transports` key, logs go to stdout/stderr. Add a `transports` key to write to file, database, or anywhere else — all transports run concurrently.
289
-
290
- There is also a pre-configured default instance you can import directly:
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
- ```typescript
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
- The `error` method also accepts an `Error` object directly — the full cause chain and standard Node.js fields (`code`, `statusCode`, `errno`, `syscall`) are serialized automatically:
75
+ Output (colors omitted, stack trace shortened):
316
76
 
317
- ```typescript
318
- await logger.error(new Error('Connection refused'));
319
-
320
- // With extra metadata alongside:
321
- await logger.error(new Error('Payment declined'), { orderId: 'ord_123', retryable: true });
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
- You can also define **custom levels** for your domain:
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
- // Custom level methods are available immediately, fully typed
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
- **Auto-palette colors** — if you omit a color for a custom level, logixia assigns one automatically from the palette `magenta → cyan → yellow → green → blue` (cycling). No more "uncolored" custom levels blending into terminal noise.
88
+ ## Feature tour
368
89
 
369
- **NestJS service IntelliSense** — `LogixiaLoggerService.create<T>(config)` carries the level names into the return type, so the IDE autocompletes `service.kafka(...)`, `service.payment(...)` etc. with the correct signature:
90
+ ### Request tracing
370
91
 
371
92
  ```typescript
372
- const svc = LogixiaLoggerService.create({
373
- levelOptions: {
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
- ### Structured logging
96
+ const logger = createLogger({ appName: 'api', traceId: true });
384
97
 
385
- Every log call accepts a metadata object as its second argument — serialized as structured fields alongside the message, never concatenated into a string:
98
+ app.use(createExpressContextMiddleware({ responseHeader: 'X-Trace-Id' }));
99
+ app.use(createExpressMiddleware(logger)); // one request/response log line per call
386
100
 
387
- ```typescript
388
- await logger.info('User authenticated', {
389
- userId: 'usr_123',
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
- Development output (colorized text):
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
- Production output (JSON, via `format: { json: true }`):
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
- ### Child loggers
111
+ ### NestJS
418
112
 
419
- Create child loggers that inherit their parent's configuration and transport setup, but carry their own context string and optional extra fields:
113
+ Everything NestJS-specific lives in `logixia/nest`, so plain Node apps never load `@nestjs/common`.
420
114
 
421
115
  ```typescript
422
- const reqLogger = logger.child('OrderService', {
423
- requestId: req.id,
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
- ```bash
446
- LOGIXIA_LEVEL=debug node server.js
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
- Or change it at runtime:
450
-
451
- ```typescript
452
- logger.setLevel('debug');
453
- console.log(logger.getLevel()); // 'debug'
454
- ```
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/).
455
132
 
456
- ### Per-namespace log levels
133
+ → [NestJS guide](https://logixia.github.io/logixia/guide/nestjs)
457
134
 
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
- environment: 'production',
464
- namespaceLevels: {
465
- db: 'debug', // child('db') and child('db.queries') → DEBUG
466
- 'db.*': 'debug', // wildcard: all db.* children
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
- const dbLogger = logger.child('db'); // resolves to DEBUG
473
- const httpLogger = logger.child('http.req'); // resolves to WARN
474
- ```
475
-
476
- Environment variable overrides use the pattern `LOGIXIA_LEVEL_<NS>` where `<NS>` is the first segment of the namespace, uppercased:
477
-
478
- ```bash
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
147
+ await logger.info('Login', { user: 'alice', password: 'hunter2' });
148
+ // → {"user":"alice","password":"[REDACTED]"}
484
149
  ```
485
150
 
486
- ---
151
+ Paths support `*` (one segment) and `**` (any depth), match case-insensitively, and the input object is never mutated. Circular references are printed as `[Circular]`.
487
152
 
488
- ## Transports
153
+ → [Redaction](https://logixia.github.io/logixia/guide/redaction)
489
154
 
490
- All transports are configured under the `transports` key and run concurrently on every log call.
155
+ ### Transports
491
156
 
492
- ### Console
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
- const logger = createLogger({
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
- ```
160
+ import { createLogger, OtlpLogTransport, SlackTransport } from 'logixia';
510
161
 
511
- ### File with rotation
512
-
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', // rotate before the file passes this size ('500KB', '1GB' or bytes)
524
- maxFiles: 14, // keep the 14 newest rotated files, delete older ones
525
- zippedArchive: true, // compress old files with gzip
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
- The live file keeps its name, so `tail -f logs/app.log` keeps working. Rotated files are renamed beside it: `app-2026-10-22T09-30-00-000.log.gz`, or `app-2026-10-22.log.gz` when `datePattern` is set. Files are split on line boundaries, so no line is cut in half. On restart the transport carries on from the size of the existing `app.log`.
536
-
537
- **Bounding memory under load.** Entries wait in memory until the disk accepts them, and by default there is no limit. Set `maxQueue` to cap how many unwritten entries (the batch being written included) the transport holds, and `queuePolicy` to choose what happens at the cap:
538
-
539
- ```typescript
540
- file: {
541
- filename: 'app.log',
542
- maxQueue: 10000,
543
- queuePolicy: 'drop-oldest', // 'drop-oldest' (default) | 'drop-newest' | 'block'
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
- `drop-oldest` discards the oldest waiting entry, `drop-newest` discards the incoming one, and `block` makes `write()` wait until there is room. Dropped lines are counted and reported on stderr at most once every 5 seconds, and once more on `close()`.
196
+ → [All transports](https://logixia.github.io/logixia/guide/transports/)
548
197
 
549
- You can also use **time-based rotation** via the `rotation` sub-key:
198
+ ### OpenTelemetry
550
199
 
551
200
  ```typescript
552
- transports: {
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
- ```
567
-
568
- With a `rotation` block every file is timestamped, including the one being written.
201
+ import { createLogger, initOtelBridge } from 'logixia';
569
202
 
570
- Multiple file transports are supported — pass an array:
203
+ initOtelBridge(); // call once, after your OTel SDK starts
571
204
 
572
- ```typescript
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
- ### Database
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
- // SQLite — great for local development and small apps
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
- Multiple database targets are supported — pass an array:
214
+ ### Browser and edge
650
215
 
651
216
  ```typescript
652
- transports: {
653
- database: [
654
- { type: 'postgresql', host: 'primary-db', database: 'appdb', table: 'logs' },
655
- { type: 'mongodb', connectionString: process.env.MONGO_URI, database: 'appdb', collection: 'logs' },
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
- ### Analytics
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
- logixia includes built-in support for Datadog, Mixpanel, Segment, and Google Analytics. All analytics transports are batched and non-blocking.
225
+ → [Browser and edge](https://logixia.github.io/logixia/guide/browser-and-edge)
663
226
 
664
- **Datadog** — sends logs, metrics, and traces to your Datadog account:
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
- environment: 'production',
672
- transports: {
673
- analytics: {
674
- datadog: {
675
- apiKey: process.env.DD_API_KEY!,
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
- **Mixpanel:**
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
- ```typescript
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
- **Google Analytics:**
245
+ ### Metrics
718
246
 
719
247
  ```typescript
720
- transports: {
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
- ```typescript
736
- const logger = createLogger({
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
- // One call → console + file + postgres + datadog. All concurrent. All non-blocking.
758
- await logger.info('Order placed', { orderId: 'ord_789' });
255
+ await logger.info('request handled', { method: 'GET', status: 200 });
256
+ app.get('/metrics', metrics.expressHandler()); // Prometheus text format
759
257
  ```
760
258
 
761
- ### Slack and Discord webhook transports
762
-
763
- Send alerts to a chat channel. Both default to `level: 'error'`, batch entries that arrive within `minIntervalMs` (default 1000) into one message, and cap a message at 10 entries (the rest are dropped and counted in the message).
259
+ → [Metrics](https://logixia.github.io/logixia/guide/metrics)
764
260
 
765
- ```typescript
766
- import { createLogger } from 'logixia';
767
- import { DiscordTransport, SlackTransport } from 'logixia';
261
+ ### CLI and log search
768
262
 
769
- const logger = createLogger({
770
- appName: 'api',
771
- transports: {
772
- custom: [
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
- - The webhook URL is a secret. It must be `https:` on `hooks.slack.com` (Slack) or `discord.com` / `discordapp.com` (Discord) unless you pass `allowCustomUrl: true`. It is never logged and never appears in error messages (only the host does).
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
- ### Custom transport
271
+ → [CLI](https://logixia.github.io/logixia/guide/cli) · [Log search](https://logixia.github.io/logixia/guide/log-search)
787
272
 
788
- Implement `ITransport` to send logs anywhere — Slack, PagerDuty, S3, an internal queue:
273
+ ### More
789
274
 
790
- ```typescript
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
- const logger = createLogger({
814
- appName: 'api',
815
- environment: 'production',
816
- transports: {
817
- custom: [new SlackTransport()],
818
- },
819
- });
820
- ```
277
+ ## Upgrading from 1.x
821
278
 
822
- The `write` method may return `void` or `Promise<void>` — both are supported. The `TransportLogEntry` shape is:
279
+ 2.0 has one breaking change: everything NestJS-specific moved from the root entry to `logixia/nest`.
823
280
 
824
- ```typescript
825
- interface TransportLogEntry {
826
- timestamp: Date;
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
- ## Cloud adapters
288
+ ## Security
840
289
 
841
- logixia ships three production-ready cloud transports. All are batched, non-blocking, and implement the same `flush()` / `close()` lifecycle as the built-in transports. Import and pass directly to the `custom` transport array.
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
- ### AWS CloudWatch
295
+ → [Security guide](https://logixia.github.io/logixia/guide/security)
844
296
 
845
- Batches log events and sends them to a CloudWatch Logs stream using `PutLogEvents`. Supports **EMF** (Embedded Metric Format) so numeric fields in your log entries are automatically promoted to CloudWatch Metrics — no separate SDK needed.
297
+ ## Documentation and examples
846
298
 
847
- ```typescript
848
- import { CloudWatchTransport } from 'logixia/cloudwatch';
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
- const logger = createLogger({
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
- With `emf: true`, any numeric field in your log data is published as a CloudWatch Metric in the `Logixia` namespace (override with `emfNamespace`), with a `level` dimension:
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
- ```typescript
873
- await logger.info('Request completed', { duration: 142, statusCode: 200 });
874
- // → CloudWatch Metrics in namespace "Logixia": duration, statusCode
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
- **Credential sources**, tried in this order (no SDK needed):
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
- ### Azure Monitor
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
- Sends logs to Azure Monitor via the **Logs Ingestion API** (Data Collection Rule). Uses OAuth2 client-credentials to obtain a bearer token automatically.
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 { createExpressContextMiddleware, createFastifyContextHook } from 'logixia';
986
-
987
- // Express: trace ID = first present header of traceIdHeader (default
988
- // DEFAULT_TRACE_HEADERS: traceparent, x-trace-id, x-request-id, x-correlation-id,
989
- // trace-id; traceparent is parsed to its 32-hex trace id), else request.id, else a
990
- // generated UUID. The ID is available via getCurrentTraceId() and is the logger's
991
- // top-level traceId for everything inside the request. No response header is set
992
- // unless you ask for one with responseHeader.
993
- app.use(createExpressContextMiddleware({ responseHeader: 'X-Trace-Id' }));
994
-
995
- // Fastify: same resolution; fastify's default counter ids ('req-1', ...) are never
996
- // used as trace IDs. Also shared with createFastifyPlugin() from 'logixia/middleware'.
997
- // traceIdHeader accepts a single name or a list; responseHeader works as above.
998
- fastify.addHook('onRequest', createFastifyContextHook());
999
-
1000
- // Prefer full control? Wrap the request yourself:
1001
- app.use((req, res, next) => {
1002
- const traceId =
1003
- extractTraceId(req, { header: DEFAULT_TRACE_HEADERS, query: ['traceId'] }) ?? generateTraceId();
1004
- res.setHeader('X-Trace-Id', traceId);
1005
- runWithTraceId(traceId, () => next());
1006
- });
1007
-
1008
- // Request/response logging (separate from tracing) lives in 'logixia/middleware':
1009
- // app.use(createExpressMiddleware(logger));
1010
- // await fastify.register(createFastifyPlugin(logger));
1011
- // NestJS: import { TraceMiddleware } from 'logixia/nest';
1012
-
1013
- // Service layer — no parameters needed, trace ID propagates automatically
1014
- class OrderService {
1015
- async createOrder(data: OrderData) {
1016
- await logger.info('Creating order', { items: data.items.length });
1017
- // ^ trace ID is automatically included
1018
- await this.processPayment(data);
1019
- }
1020
-
1021
- async processPayment(data: OrderData) {
1022
- await logger.info('Processing payment', { amount: data.total });
1023
- // ^ same trace ID, propagated automatically through await
1024
- }
1025
- }
1026
- ```
1027
-
1028
- 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`.
1029
-
1030
- ### Hono
1031
-
1032
- On Node, Hono needs nothing beyond `runWithTraceId()` — wrap `await next()` and every log line in the request carries the trace ID:
1033
-
1034
- ```typescript
1035
- app.use(async (c, next) => {
1036
- const traceId =
1037
- extractTraceId({ headers: c.req.header() }, { header: DEFAULT_TRACE_HEADERS }) ??
1038
- generateTraceId();
1039
- await runWithTraceId(traceId, next);
1040
- });
1041
- ```
1042
-
1043
- See [`examples/hono-example.ts`](./examples/hono-example.ts) (`npm run dev:hono`) for the full version.
1044
-
1045
- ### Next.js (App Router)
1046
-
1047
- 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.
1048
-
1049
- ### NestJS trace middleware
1050
-
1051
- The `TraceMiddleware` class integrates directly with NestJS's middleware system. `LogixiaLoggerModule.forRoot()` applies it automatically across all routes — no manual wiring needed:
1052
-
1053
- ```typescript
1054
- // Applied automatically by LogixiaLoggerModule.forRoot().
1055
- // For manual use in a custom module:
1056
-
1057
- import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
1058
- import { TraceMiddleware } from 'logixia/nest';
1059
-
1060
- @Module({})
1061
- export class AppModule implements NestModule {
1062
- configure(consumer: MiddlewareConsumer) {
1063
- consumer.apply(TraceMiddleware).forRoutes('*');
1064
- }
1065
- }
1066
- ```
1067
-
1068
- ### Kafka trace interceptor
1069
-
1070
- 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:
1071
-
1072
- ```typescript
1073
- import { KafkaTraceInterceptor } from 'logixia/nest';
1074
- import { UseInterceptors, Controller } from '@nestjs/common';
1075
- import { MessagePattern } from '@nestjs/microservices';
1076
-
1077
- @UseInterceptors(new KafkaTraceInterceptor())
1078
- @Controller()
1079
- export class OrdersConsumer {
1080
- @EventPattern('order.created')
1081
- async handle(@Payload() data: OrderCreatedEvent) {
1082
- // getCurrentTraceId() works here — extracted from the Kafka message body/headers
1083
- await logger.info('Processing order event', { orderId: data.orderId });
1084
- }
1085
- }
1086
- ```
1087
-
1088
- > **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.
1089
-
1090
- ### WebSocket trace interceptor
1091
-
1092
- Propagates trace IDs through WebSocket event handlers. Reads `traceId` from the message body, event payload, or handshake query:
1093
-
1094
- ```typescript
1095
- import { WebSocketTraceInterceptor } from 'logixia/nest';
1096
- import { UseInterceptors, WebSocketGateway, SubscribeMessage } from '@nestjs/websockets';
1097
-
1098
- @UseInterceptors(new WebSocketTraceInterceptor())
1099
- @WebSocketGateway({ namespace: '/events', cors: { origin: '*' } })
1100
- export class EventsGateway {
1101
- @SubscribeMessage('ping')
1102
- async handlePing(@MessageBody() data: { traceId?: string }) {
1103
- // trace ID propagated from the WS message body / handshake headers
1104
- await logger.info('WS ping received', { traceId: getCurrentTraceId() });
1105
- }
1106
- }
1107
- ```
1108
-
1109
- > **Note:** Pass `new WebSocketTraceInterceptor()` (an instance), not the class reference. `UseInterceptors` must be imported from `@nestjs/common`, not `@nestjs/websockets`.
1110
-
1111
- ````
1112
-
1113
- ---
1114
-
1115
- ## NestJS integration
1116
-
1117
- > **Upgrading from 1.x:** since 2.0.0, everything NestJS-specific lives in `logixia/nest` only.
1118
- > Change `import { LogixiaLoggerModule, LogixiaLoggerService, LogixiaExceptionFilter, InjectLogger, LogMethod } from 'logixia'`
1119
- > to `... from 'logixia/nest'`. The root `logixia` entry no longer loads `@nestjs/common`, so plain Node, Express and Fastify apps
1120
- > work without NestJS installed.
1121
-
1122
- Drop-in module with zero boilerplate. Registers `TraceMiddleware` for all routes, provides `LogixiaLoggerService`, `KafkaTraceInterceptor`, and `WebSocketTraceInterceptor` via the global DI container.
1123
-
1124
- > **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.
1125
- >
1126
- > ```bash
1127
- > cd examples/nestjs-app
1128
- > cp .env.example .env
1129
- > docker compose up -d
1130
- > curl http://localhost:3000/health
1131
- > ```
1132
-
1133
- ```typescript
1134
- // app.module.ts
1135
- import { Module } from '@nestjs/common';
1136
- import { LogixiaLoggerModule } from 'logixia/nest';
1137
-
1138
- @Module({
1139
- imports: [
1140
- LogixiaLoggerModule.forRoot({
1141
- appName: 'nestjs-api',
1142
- environment: process.env.NODE_ENV ?? 'development',
1143
- traceId: true,
1144
- transports: {
1145
- console: {},
1146
- file: { filename: 'app.log', dirname: './logs', maxSize: '50MB' },
1147
- },
1148
- }),
1149
- ],
1150
- })
1151
- export class AppModule {}
1152
- ````
1153
-
1154
- **Async configuration** (for credentials from a config service):
1155
-
1156
- ```typescript
1157
- LogixiaLoggerModule.forRootAsync({
1158
- imports: [ConfigModule],
1159
- useFactory: async (config: ConfigService) => ({
1160
- appName: 'nestjs-api',
1161
- environment: config.get('NODE_ENV'),
1162
- traceId: true,
1163
- transports: {
1164
- database: {
1165
- type: 'postgresql',
1166
- host: config.get('DB_HOST'),
1167
- database: config.get('DB_NAME'),
1168
- password: config.get('DB_PASSWORD'),
1169
- table: 'logs',
1170
- },
1171
- },
1172
- }),
1173
- inject: [ConfigService],
1174
- });
1175
- ```
1176
-
1177
- **Inject the logger** in any service or controller. Since `LogixiaLoggerModule` is globally scoped, no per-module import is needed:
1178
-
1179
- ```typescript
1180
- // orders.service.ts
1181
- import { Injectable } from '@nestjs/common';
1182
- import { LogixiaLoggerService } from 'logixia/nest';
1183
-
1184
- @Injectable()
1185
- export class OrdersService {
1186
- constructor(private readonly logger: LogixiaLoggerService) {}
1187
-
1188
- async createOrder(dto: CreateOrderDto) {
1189
- await this.logger.info('Creating order', { userId: dto.userId });
1190
- // ...
1191
- }
1192
- }
1193
- ```
1194
-
1195
- **Feature-scoped child logger** — create a logger pre-scoped to a specific context string:
1196
-
1197
- ```typescript
1198
- // orders.module.ts
1199
- import { Module } from '@nestjs/common';
1200
- import { LogixiaLoggerModule } from 'logixia/nest';
1201
- import { OrdersService } from './orders.service';
1202
-
1203
- @Module({
1204
- imports: [LogixiaLoggerModule.forFeature('OrdersModule')],
1205
- providers: [OrdersService],
1206
- })
1207
- export class OrdersModule {}
1208
- ```
1209
-
1210
- ```typescript
1211
- // orders.service.ts — inject the feature-scoped token
1212
- import { Inject, Injectable } from '@nestjs/common';
1213
- import { LOGIXIA_LOGGER_PREFIX, LogixiaLoggerService } from 'logixia/nest';
1214
-
1215
- @Injectable()
1216
- export class OrdersService {
1217
- constructor(
1218
- @Inject(`${LOGIXIA_LOGGER_PREFIX}ORDERSMODULE`)
1219
- private readonly logger: LogixiaLoggerService
1220
- ) {}
1221
- }
1222
- ```
1223
-
1224
- `LogixiaLoggerService` exposes the full `LogixiaLogger` API: `info`, `warn`, `error`, `debug`, `trace`, `verbose`, `logLevel`, `time`, `timeEnd`, `timeAsync`, `setLevel`, `getLevel`, `setContext`, `child`, `close`, `getCurrentTraceId`, and more.
1225
-
1226
- **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:
1227
-
1228
- ```typescript
1229
- const logger = LogixiaLoggerService.create({
1230
- levelOptions: {
1231
- levels: { error: 0, warn: 1, info: 2, kafka: 2, mysql: 2, payment: 1 },
1232
- colors: {
1233
- error: 'red',
1234
- warn: 'yellow',
1235
- info: 'blue',
1236
- kafka: 'magenta',
1237
- payment: 'brightYellow',
1238
- },
1239
- },
1240
- });
1241
-
1242
- // Methods are created at construction time — no casting required
1243
- await logger.kafka('Consumer rebalanced', { groupId: 'app-group' });
1244
- await logger.mysql('Slow query detected', { query, ms: 1240 });
1245
- await logger.payment('Charge captured', { txnId, amount: 99.99 });
1246
- ```
1247
-
1248
- **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):
1249
-
1250
- ```typescript
1251
- import { runWithTraceId } from 'logixia';
1252
-
1253
- await runWithTraceId(String(req.headers['x-request-id']), async () => {
1254
- await logger.info('Request received'); // traceId: "abc-123" in every line
1255
- await logger.kafka('Event published'); // same traceId
1256
- });
1257
-
1258
- // Read the active traceId at any point
1259
- const traceId = logger.getCurrentTraceId();
1260
- ```
1261
-
1262
- ### @LogMethod decorator
1263
-
1264
- 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).
1265
-
1266
- ```typescript
1267
- import { Injectable } from '@nestjs/common';
1268
- import { LogixiaLoggerService, LogMethod } from 'logixia/nest';
1269
-
1270
- @Injectable()
1271
- export class PaymentService {
1272
- constructor(private readonly logger: LogixiaLoggerService) {}
1273
-
1274
- // Logs entry with args, exit with duration, and errors with full stack trace
1275
- @LogMethod({ level: 'info', logArgs: true, logResult: false })
1276
- async processPayment(orderId: string, amount: number): Promise<void> {
1277
- // your business logic — no try/catch needed for logging
1278
- }
1279
-
1280
- // Minimal — just tracks duration at debug level
1281
- @LogMethod()
1282
- async fetchExchangeRate(currency: string): Promise<number> {
1283
- return 1.0;
1284
- }
1285
- }
1286
- ```
1287
-
1288
- `@LogMethod` options:
1289
-
1290
- | Option | Type | Default | Description |
1291
- | ----------- | -------------------------------- | --------- | ------------------------------------------------------- |
1292
- | `level` | `'debug' \| 'info' \| 'verbose'` | `'debug'` | Log level for entry / exit messages |
1293
- | `logArgs` | `boolean` | `true` | Include method arguments in the entry log |
1294
- | `logResult` | `boolean` | `false` | Include the return value in the exit log |
1295
- | `logErrors` | `boolean` | `true` | Log errors with stack trace when the method throws |
1296
- | `label` | `string` | auto | Override the auto-detected `ClassName.methodName` label |
1297
-
1298
- ### LogixiaExceptionFilter
1299
-
1300
- 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.
1301
-
1302
- ```typescript
1303
- // main.ts
1304
- import { NestFactory } from '@nestjs/core';
1305
- import { LogixiaExceptionFilter } from 'logixia/nest';
1306
- import { AppModule } from './app.module';
1307
-
1308
- async function bootstrap() {
1309
- const app = await NestFactory.create(AppModule);
1310
- app.useGlobalFilters(new LogixiaExceptionFilter());
1311
- await app.listen(3000);
1312
- }
1313
- bootstrap();
1314
- ```
1315
-
1316
- With `LogixiaLoggerModule` set up, inject the service directly so it logs to your configured transports:
1317
-
1318
- ```typescript
1319
- import { APP_FILTER } from '@nestjs/core';
1320
- import { LogixiaExceptionFilter, LogixiaLoggerService } from 'logixia/nest';
1321
-
1322
- // In AppModule providers:
1323
- {
1324
- provide: APP_FILTER,
1325
- useFactory: (logger: LogixiaLoggerService) => new LogixiaExceptionFilter(logger),
1326
- inject: [LogixiaLoggerService],
1327
- }
1328
- ```
1329
-
1330
- The filter returns a consistent structured shape on every error:
1331
-
1332
- ```json
1333
- {
1334
- "success": false,
1335
- "error": {
1336
- "type": "validation_error",
1337
- "code": "ORD-001",
1338
- "message": "Order amount must be greater than zero.",
1339
- "param": "amount"
1340
- },
1341
- "meta": {
1342
- "request_id": "req_8579ef8ff7a64c3cbc752fd1cb9852df",
1343
- "timestamp": "2026-03-24T16:30:19.217Z",
1344
- "path": "/orders/boom",
1345
- "status": 400
1346
- },
1347
- "debug": {
1348
- "stack": "LogixiaException: Order amount must be greater than zero.\n at ..."
1349
- }
1350
- }
1351
- ```
1352
-
1353
- 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:
1354
-
1355
- ```typescript
1356
- new LogixiaExceptionFilter(undefined, undefined, {
1357
- exposeDebug: process.env.NODE_ENV === 'development',
1358
- // exposeCause: true, // also include the underlying error message (may contain internal detail)
1359
- });
1360
- ```
1361
-
1362
- And every log line carries `method`, `url`, `status`, `request_id` as structured fields — not a plain string:
1363
-
1364
- ```
1365
- WARN [NestApplication] (traceId) [ORD-001] Order amount must be greater than zero.
1366
- { method: "GET", url: "/orders/boom", status: 400, request_id: "req_..." }
1367
-
1368
- ERROR [NestApplication] (traceId) DB connection timed out after 5000ms
1369
- { method: "GET", url: "/orders/crash", status: 500, request_id: "req_...", error: { ... } }
1370
- ```
1371
-
1372
- ---
1373
-
1374
- ## Correlation ID propagation
1375
-
1376
- `import { ... } from 'logixia/correlation'`
1377
-
1378
- 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.
1379
-
1380
- 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.
1381
-
1382
- ### Correlation Express middleware
1383
-
1384
- ```typescript
1385
- import { correlationMiddleware } from 'logixia/correlation';
1386
-
1387
- // Zero-config — reads X-Correlation-ID from the incoming request.
1388
- // Generates a UUID v4 if the header is missing or invalid.
1389
- // Sets X-Correlation-ID on the response.
1390
- app.use(correlationMiddleware());
1391
-
1392
- // Custom config:
1393
- app.use(
1394
- correlationMiddleware({
1395
- header: 'X-Correlation-ID', // header to read / write (matched case-insensitively). Default: 'x-correlation-id'
1396
- generate: () => crypto.randomUUID(), // used when no usable incoming ID. Default: crypto.randomUUID()
1397
- trustIncoming: true, // honour the header from the client. Default: true
1398
- setResponseHeader: true, // echo the ID back in the response. Default: true
1399
- })
1400
- );
1401
- ```
1402
-
1403
- An incoming ID is only used if it is at most 128 characters long and contains only
1404
- `A-Z`, `a-z`, `0-9`, `.`, `_`, `:` and `-`. Anything else (newlines, spaces, or base64-style
1405
- `+`, `/`, `=`) is replaced with a freshly generated ID, so client-supplied text can't end up in
1406
- every log line. Set `trustIncoming: false` to ignore the incoming header entirely and always
1407
- generate a new ID. `correlationFastifyHook()` takes the same options.
1408
-
1409
- ### Correlation Fastify hook
1410
-
1411
- ```typescript
1412
- import Fastify from 'fastify';
1413
- import { correlationFastifyHook } from 'logixia/correlation';
1414
-
1415
- const app = Fastify();
1416
- app.addHook('onRequest', correlationFastifyHook());
1417
- ```
1418
-
1419
- > **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.
1420
-
1421
- ### Outbound fetch / axios
1422
-
1423
- Every outbound HTTP call made inside a correlated context automatically carries the `X-Correlation-ID` header.
1424
-
1425
- **fetch:**
1426
-
1427
- ```typescript
1428
- import { correlationFetch } from 'logixia/correlation';
1429
-
1430
- // Drop-in replacement for global fetch — forwards the active correlation ID automatically
1431
- const res = await correlationFetch('https://inventory-service/api/items', {
1432
- method: 'GET',
1433
- headers: { Authorization: `Bearer ${token}` },
1434
- });
1435
- ```
1436
-
1437
- **axios:**
1438
-
1439
- ```typescript
1440
- import axios from 'axios';
1441
- import { createCorrelationAxiosInterceptor } from 'logixia/correlation';
1442
-
1443
- const client = axios.create({ baseURL: 'https://inventory-service' });
1444
- createCorrelationAxiosInterceptor(client); // attaches X-Correlation-ID to every request
1445
- ```
1446
-
1447
- ### Kafka / SQS helpers
1448
-
1449
- ```typescript
1450
- import {
1451
- buildKafkaCorrelationHeaders, // → { 'X-Correlation-ID': '...', 'X-Request-ID': '...' }
1452
- extractMessageCorrelationId, // read correlationId from a Kafka/SQS message body
1453
- childFromRequest, // create a child logger pre-seeded with request context
1454
- withCorrelationId, // run a callback inside an explicit correlation context
1455
- getCurrentCorrelationId, // read the active correlation ID (or undefined)
1456
- generateCorrelationId, // generate a new UUID v4 correlation ID
1457
- } from 'logixia/correlation';
1458
-
1459
- // Kafka producer — attach correlation headers to every message
1460
- const producer = kafka.producer();
1461
- await producer.send({
1462
- topic: 'orders',
1463
- messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],
1464
- });
1465
-
1466
- // Kafka consumer — restore context for the handler
1467
- const correlationId = extractMessageCorrelationId(message.value);
1468
- withCorrelationId(correlationId, async () => {
1469
- await orderService.process(message.value);
1470
- // all logger.* calls inside carry correlationId automatically
1471
- });
1472
-
1473
- // Create a child logger pre-loaded with request identifiers
1474
- const reqLogger = childFromRequest(logger, req);
1475
- await reqLogger.info('Order created', { orderId: 'ord_123' });
1476
- // → log includes correlationId, requestId, originService
1477
- ```
1478
-
1479
- **Standalone context (no HTTP framework):**
1480
-
1481
- ```typescript
1482
- import { withCorrelationId, generateCorrelationId } from 'logixia/correlation';
1483
-
1484
- const id = generateCorrelationId(); // UUID v4
1485
-
1486
- withCorrelationId(id, async () => {
1487
- await processJob(job);
1488
- // all nested logger calls carry id
1489
- });
1490
- ```
1491
-
1492
- ---
1493
-
1494
- ## Browser support
1495
-
1496
- `import { ... } from 'logixia/browser'`
1497
-
1498
- 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.
1499
-
1500
- ```typescript
1501
- import { createBrowserLogger } from 'logixia/browser';
1502
-
1503
- const logger = createBrowserLogger({
1504
- appName: 'my-app',
1505
- minLevel: 'info',
1506
- pretty: true, // colorized dev-friendly output via console.group
1507
- });
1508
-
1509
- logger.info('App loaded', { route: '/home' });
1510
- logger.warn('Feature flag missing', { flag: 'new-checkout' });
1511
- logger.error('API call failed', { url: '/api/orders', status: 500 });
1512
- ```
1513
-
1514
- **Browser console transport** — uses the native `console` API and maps levels to their correct methods (`console.error`, `console.warn`, `console.info`, `console.debug`):
1515
-
1516
- ```typescript
1517
- import { BrowserLogger, BrowserConsoleTransport } from 'logixia/browser';
1518
-
1519
- const logger = new BrowserLogger({
1520
- appName: 'my-app',
1521
- transports: [new BrowserConsoleTransport({ pretty: true })],
1522
- });
1523
- ```
1524
-
1525
- **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:
1526
-
1527
- ```typescript
1528
- import { BrowserLogger, BrowserRemoteTransport } from 'logixia/browser';
1529
-
1530
- const logger = new BrowserLogger({
1531
- appName: 'my-app',
1532
- transports: [
1533
- new BrowserRemoteTransport({
1534
- endpoint: 'https://logs.my-company.com/ingest',
1535
- batchSize: 20, // flush when 20 entries accumulate
1536
- flushIntervalMs: 5000, // or every 5 seconds, whichever comes first
1537
- headers: { Authorization: `Bearer ${TOKEN}` },
1538
- minLevel: 'warn', // only ship warn+ to the remote endpoint
1539
- }),
1540
- ],
1541
- });
1542
- ```
1543
-
1544
- Delivery failures (network errors and non-OK HTTP statuses) are counted in `failedFlushes` / `lastError` and passed to the optional `onError` callback. A throttled (at most one per minute) console report containing only the error name, HTTP status and endpoint host is printed everywhere except real browser pages, where it is silent by default; set `silent: true` or `silent: false` to override. Entries written to the transport from inside `onError` or while a report is printed are dropped (counted in `dropped`) to avoid feedback loops.
1545
-
1546
- **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.
1547
-
1548
- The following utilities from the main package are also re-exported from `logixia/browser` (safe for non-Node runtimes):
1549
-
1550
- ```typescript
1551
- import {
1552
- createTypedLogger, // typed schema-enforced logger factory
1553
- defineLogSchema, // define a compile-time log schema
1554
- createOtelBridge, // OpenTelemetry bridge
1555
- isOtelActive,
1556
- withOtelSpan,
1557
- } from 'logixia/browser';
1558
- ```
1559
-
1560
- ---
1561
-
1562
- ## Log redaction
1563
-
1564
- 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.
1565
-
1566
- **Path-based redaction** supports dot-notation, `*` (single segment wildcard), and `**` (any-depth wildcard):
1567
-
1568
- ```typescript
1569
- const logger = createLogger({
1570
- appName: 'api',
1571
- environment: 'production',
1572
- redact: {
1573
- paths: [
1574
- 'password',
1575
- 'token',
1576
- 'accessToken',
1577
- 'refreshToken',
1578
- '*.secret', // any field named 'secret' at one level deep
1579
- 'req.headers.*', // all headers
1580
- 'user.creditCard', // nested path
1581
- '**.password', // 'password' at any depth
1582
- ],
1583
- censor: '[REDACTED]', // default if omitted
1584
- },
1585
- });
1586
-
1587
- await logger.info('User login', {
1588
- username: 'alice',
1589
- password: 'hunter2', // → '[REDACTED]'
1590
- token: 'eyJhbGc...', // → '[REDACTED]'
1591
- user: {
1592
- creditCard: '4111...', // → '[REDACTED]'
1593
- email: 'alice@example.com', // untouched
1594
- },
1595
- });
1596
- ```
1597
-
1598
- **Regex-based redaction** — mask patterns in string values across all fields:
1599
-
1600
- ```typescript
1601
- const logger = createLogger({
1602
- appName: 'api',
1603
- environment: 'production',
1604
- redact: {
1605
- patterns: [
1606
- /Bearer\s+\S+/gi, // Authorization header values
1607
- /sk-[a-z0-9]{32,}/gi, // OpenAI / Stripe secret keys
1608
- /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, // credit card numbers
1609
- ],
1610
- },
1611
- });
1612
- ```
1613
-
1614
- Both `paths` and `patterns` can be combined in the same config.
1615
-
1616
- Path rules follow these conventions:
1617
-
1618
- - Matching is case-insensitive, so `req.headers.authorization` also redacts `Authorization`.
1619
- - 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.
1620
- - Circular references are printed as `[Circular]` instead of throwing.
1621
- - 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.
1622
-
1623
- ---
1624
-
1625
- ## Timer API
1626
-
1627
- Measure the duration of any operation — synchronous or async. The result is logged automatically when the timer ends:
1628
-
1629
- ```typescript
1630
- // Manual start/stop
1631
- logger.time('db-query');
1632
- const rows = await db.query('SELECT * FROM orders');
1633
- await logger.timeEnd('db-query');
1634
- // → logs: Timer 'db-query' finished { duration: '42ms', startTime: '...', endTime: '...' }
1635
-
1636
- // Wrap an async function — timer starts before and stops after, even if the function throws
1637
- const result = await logger.timeAsync('process-batch', async () => {
1638
- return await processBatch(items);
1639
- });
1640
- ```
1641
-
1642
- `timeEnd` returns the duration in milliseconds so you can use it in your own logic:
1643
-
1644
- ```typescript
1645
- const ms = await logger.timeEnd('db-query');
1646
- if (ms && ms > 500) {
1647
- await logger.warn('Slow query detected', { durationMs: ms });
1648
- }
1649
- ```
1650
-
1651
- ---
1652
-
1653
- ## Field management
1654
-
1655
- Control which fields appear in log output at runtime, without changing config:
1656
-
1657
- ```typescript
1658
- // Disable fields you don't need in a specific context
1659
- logger.disableField('traceId');
1660
- logger.disableField('appName');
1661
-
1662
- // Re-enable them
1663
- logger.enableField('traceId');
1664
-
1665
- // Check whether a field is currently active
1666
- const isOn = logger.isFieldEnabled('timestamp'); // true
1667
-
1668
- // Inspect the current state of all fields
1669
- const state = logger.getFieldState();
1670
- // → { timestamp: true, level: true, appName: false, traceId: false, ... }
1671
-
1672
- // Reset all fields back to the config defaults
1673
- logger.resetFieldState();
1674
- ```
1675
-
1676
- Available field names: `timestamp`, `level`, `appName`, `service`, `traceId`, `message`, `payload`, `timeTaken`, `context`, `requestId`, `userId`, `sessionId`, `environment`.
1677
-
1678
- ---
1679
-
1680
- ## Transport level control
1681
-
1682
- 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:
1683
-
1684
- ```typescript
1685
- // Only send errors to the database transport — no noise from info/debug
1686
- logger.setTransportLevels('database-0', ['error', 'warn']);
1687
-
1688
- // Check what levels a transport is currently configured for
1689
- const levels = logger.getTransportLevels('database-0'); // ['error', 'warn']
1690
-
1691
- // List all registered transport IDs
1692
- const ids = logger.getAvailableTransports(); // ['console', 'file-0', 'database-0']
1693
-
1694
- // Remove all level overrides — all transports receive everything again
1695
- logger.clearTransportLevelPreferences();
1696
- ```
1697
-
1698
- ## Transport filter predicate
1699
-
1700
- 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.
1701
-
1702
- ```typescript
1703
- const logger = createLogger({
1704
- appName: 'api',
1705
- environment: 'production',
1706
- transports: {
1707
- // Only persist error/warn entries that come from PaymentService to the DB
1708
- database: {
1709
- type: 'mongodb',
1710
- connectionString: process.env.MONGO_URI,
1711
- database: 'appdb',
1712
- collection: 'logs',
1713
- filter: (entry) =>
1714
- ['error', 'warn'].includes(entry.level) && entry.context === 'PaymentService',
1715
- },
1716
-
1717
- // Write everything to the main file, but exclude noisy health-check pings
1718
- file: {
1719
- filename: 'app.log',
1720
- dirname: './logs',
1721
- filter: (entry) => entry.message !== 'health check',
1722
- },
1723
-
1724
- // Console gets everything — no filter
1725
- console: { colorize: true },
1726
- },
1727
- });
1728
- ```
1729
-
1730
- The `filter` predicate receives a `TransportLogEntry`:
1731
-
1732
- ```typescript
1733
- interface TransportLogEntry {
1734
- timestamp: Date;
1735
- level: string; // 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'verbose' | string
1736
- message: string;
1737
- data?: Record<string, unknown>;
1738
- context?: string; // set via logger.child('MyService') or setContext()
1739
- traceId?: string;
1740
- appName?: string;
1741
- environment?: string;
1742
- }
1743
- ```
1744
-
1745
- `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.
1746
-
1747
- ---
1748
-
1749
- ## Log search
1750
-
1751
- Query your in-memory log history without shipping to Elasticsearch, Datadog, or any external service. Useful in development and lightweight production setups:
1752
-
1753
- ```typescript
1754
- import { SearchManager } from 'logixia/search';
1755
-
1756
- const search = new SearchManager({ maxIndexSize: 10_000 });
1757
-
1758
- // Index a batch of entries (from a file, database query, or any source)
1759
- await search.indexLogs(logEntries); // or: await search.indexLog(entry)
1760
-
1761
- // Search by text, with filters and options
1762
- const results = await search.search(
1763
- 'payment failed',
1764
- { levels: ['error'], timeRange: { start: new Date('2025-01-01'), end: new Date() } },
1765
- { limit: 50 }
1766
- );
1767
- // → sorted by relevance, each result is { log, score, ... }
1768
-
1769
- const nl = await search.naturalLanguageSearch('payment errors'); // plain-English query
1770
- const trace = await search.correlateByTraceId('req-001'); // every log of one request
1771
- ```
1772
-
1773
- The same code runs as [`examples/search-readme.ts`](examples/search-readme.ts).
1774
-
1775
- ---
1776
-
1777
- ## OpenTelemetry
1778
-
1779
- 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:
1780
-
1781
- ```typescript
1782
- const logger = createLogger({
1783
- appName: 'checkout-service',
1784
- environment: 'production',
1785
- traceId: {
1786
- enabled: true,
1787
- extractor: {
1788
- header: ['traceparent', 'tracestate', 'x-trace-id'],
1789
- },
1790
- },
1791
- });
1792
-
1793
- // The traceparent header from the incoming request is stored as the trace ID
1794
- // and included in every log entry automatically.
1795
- app.post('/checkout', async (req, res) => {
1796
- await logger.info('Checkout initiated', { cartId: req.body.cartId });
1797
- // → log carries the W3C traceparent from the request
1798
- });
1799
- ```
1800
-
1801
- ---
1802
-
1803
- ## OTLP logs export (OpenTelemetry-native)
1804
-
1805
- 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.
1806
-
1807
- ```typescript
1808
- import { OtlpLogTransport } from 'logixia';
1809
-
1810
- const logger = createLogger({
1811
- appName: 'api',
1812
- transports: {
1813
- custom: [
1814
- new OtlpLogTransport({
1815
- url: 'http://localhost:4318/v1/logs',
1816
- serviceName: 'api',
1817
- serviceVersion: '1.4.0',
1818
- environment: 'production',
1819
- headers: { 'x-api-key': process.env.OTLP_KEY! },
1820
- }),
1821
- ],
1822
- },
1823
- });
1824
- // Every log is exported as an OTel LogRecord with traceId/spanId for native
1825
- // trace↔log correlation. Buffers drain on close() — no loss on shutdown.
1826
- ```
1827
-
1828
- Import it from `logixia` or `logixia/transports`. Ready-made configs (URL and auth headers) for Grafana Loki / Grafana Cloud, Axiom, Better Stack and a local OpenTelemetry Collector are in [docs/otlp-recipes.md](docs/otlp-recipes.md).
1829
-
1830
- ---
1831
-
1832
- ## Wide events / canonical log lines
1833
-
1834
- 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.
1835
-
1836
- ```typescript
1837
- import { wideEventMiddleware, addEventFields } from 'logixia';
1838
-
1839
- // One canonical line per request, auto-emitted on response finish/close:
1840
- app.use(wideEventMiddleware(logger)); // pre-fills method, url, ip, status, duration
1841
-
1842
- app.get('/checkout', (req, res) => {
1843
- addEventFields({ userId: req.user.id, planTier: 'pro' }); // from anywhere
1844
- addEventFields({ dbQueries: 4, cacheHit: true });
1845
- res.json({ ok: true });
1846
- // → ONE log line: { method, url, statusCode, durationMs, userId, planTier,
1847
- // dbQueries, cacheHit, traceId } — no JOINs at query time
1848
- });
1849
- ```
1850
-
1851
- Or wrap any unit of work manually — the event is emitted once, even if the callback throws:
1852
-
1853
- ```typescript
1854
- import { withWideEvent, addEventFields } from 'logixia';
1855
-
1856
- await withWideEvent(logger, { job: 'reindex' }, async () => {
1857
- addEventFields({ shard: 3 });
1858
- await doWork(); // throws? → event still emitted with { error: true, errorMessage }
1859
- });
1860
- ```
1861
-
1862
- ---
1863
-
1864
- ## Dynamic runtime reconfiguration
1865
-
1866
- 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.
1867
-
1868
- ```typescript
1869
- import { registerLevelSignal, createLevelControlHandler } from 'logixia';
1870
-
1871
- // 1. Per-namespace level, live:
1872
- logger.setNamespaceLevels({ 'db.*': 'debug', '*': 'info' }); // db.* → debug now
1873
-
1874
- // 2. Cycle the global level with one signal (kill -USR2 <pid>):
1875
- registerLevelSignal(logger); // info → debug → trace → … → info
1876
-
1877
- // 3. Ops endpoint (mount behind your auth):
1878
- app.all('/admin/log-level', createLevelControlHandler(logger));
1879
- // GET → { level, namespaceLevels }
1880
- // POST { "level": "debug", "namespaceLevels": { "db.*": "trace" } }
1881
- ```
1882
-
1883
- ---
1884
-
1885
- ## Adaptive sampling
1886
-
1887
- 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.
1888
-
1889
- ```typescript
1890
- const logger = createLogger({
1891
- appName: 'api',
1892
- sampling: {
1893
- rate: 0.1, // keep 10% in steady state
1894
- adaptive: {
1895
- errorRateThreshold: 0.05, // ≥5% errors in the window…
1896
- boostRate: 1.0, // …keep everything until it subsides
1897
- windowMs: 10_000,
1898
- },
1899
- },
1900
- });
1901
- ```
1902
-
1903
- `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.
1904
-
1905
- ---
1906
-
1907
- ## Robust serialization
1908
-
1909
- 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:
1910
-
1911
- ```typescript
1912
- import { safeStringify, decycleValue, retrocycle } from 'logixia';
1913
-
1914
- safeStringify({ id: 9007199254740993n, self: obj }); // BigInt + cycle safe
1915
- const json = safeStringify(graph, { decycle: true }); // $ref pointers, not [Circular]
1916
- const restored = retrocycle(JSON.parse(json)); // shared refs reconstructed
1917
- ```
1918
-
1919
- ---
1920
-
1921
- ## Graceful shutdown
1922
-
1923
- Ensures all buffered log entries are flushed to every transport before the process exits. Critical for database and analytics transports that batch writes.
1924
-
1925
- > **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.
1926
-
1927
- The simplest approach is to set `gracefulShutdown: true` in config — logixia registers SIGTERM and SIGINT handlers automatically:
1928
-
1929
- ```typescript
1930
- const logger = createLogger({
1931
- appName: 'api',
1932
- environment: 'production',
1933
- gracefulShutdown: true,
1934
- transports: { database: { type: 'postgresql' /* ... */ } },
1935
- });
1936
- // SIGTERM / SIGINT will flush all transports before exit. No extra code needed.
1937
- ```
1938
-
1939
- For more control, pass a config object:
1940
-
1941
- ```typescript
1942
- const logger = createLogger({
1943
- appName: 'api',
1944
- environment: 'production',
1945
- gracefulShutdown: {
1946
- enabled: true,
1947
- timeout: 10_000, // wait up to 10 s; force-exits after
1948
- signals: ['SIGTERM', 'SIGINT', 'SIGHUP'],
1949
- exit: 'auto', // 'auto' | 'always' | 'never'
1950
- },
1951
- transports: {/* ... */},
1952
- });
1953
- ```
1954
-
1955
- **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.
1956
-
1957
- `flushOnExit` is configured by its first call; later calls with different `timeout`, `signals`, `exit` or hooks print a warning and are ignored.
1958
-
1959
- You can also call `flushOnExit` directly with lifecycle hooks:
1960
-
1961
- ```typescript
1962
- import { flushOnExit } from 'logixia';
1963
-
1964
- flushOnExit({
1965
- timeout: 5000,
1966
- beforeFlush: async () => {
1967
- // stop accepting new requests
1968
- },
1969
- afterFlush: async () => {
1970
- // any cleanup after all logs are written
1971
- },
1972
- });
1973
- ```
1974
-
1975
- Or flush and close manually — useful in Kubernetes SIGTERM handlers:
1976
-
1977
- ```typescript
1978
- process.on('SIGTERM', async () => {
1979
- // close() flushes all transports first, then closes connections.
1980
- // Any log calls that arrive during the flush (e.g. from Kafka/MongoDB
1981
- // disconnect handlers) are still written before the gate closes.
1982
- await logger.close();
1983
- process.exit(0);
1984
- });
1985
- ```
1986
-
1987
- For health monitoring:
1988
-
1989
- ```typescript
1990
- const { healthy, details } = await logger.healthCheck();
1991
- // → { healthy: true, details: { 'database-0': { ready: true, metrics: { logsWritten: 1042, ... } } } }
1992
- ```
1993
-
1994
- ---
1995
-
1996
- ## Plugin / extension API
1997
-
1998
- 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.
1999
-
2000
- ```typescript
2001
- import type { LogixiaPlugin, LogEntry } from 'logixia';
2002
- ```
2003
-
2004
- ### Writing a plugin
2005
-
2006
- ```typescript
2007
- const myPlugin: LogixiaPlugin = {
2008
- name: 'my-plugin',
2009
-
2010
- // Called once when the logger is constructed (global plugins)
2011
- // or when .use() is called (per-logger plugins).
2012
- onInit() {
2013
- console.log('Plugin initialised');
2014
- },
2015
-
2016
- // Called for every log entry before it is formatted and written.
2017
- // Return the (optionally mutated) entry to let it through,
2018
- // or return null to silently drop it.
2019
- onLog(entry: LogEntry): LogEntry | null {
2020
- // Example: enrich every entry with a deployment tag
2021
- return { ...entry, data: { ...entry.data, deployId: process.env.DEPLOY_ID } };
2022
- },
2023
-
2024
- // Called whenever logger.error() receives an Error object.
2025
- onError(error: Error, entry?: LogEntry) {
2026
- // Example: forward to a Sentry-compatible sink
2027
- externalErrorTracker.capture(error, { extra: entry?.data });
2028
- },
2029
-
2030
- // Called during logger.close() — await-able for graceful teardown.
2031
- async onShutdown() {
2032
- await flushBufferedEvents();
2033
- },
2034
- };
2035
- ```
2036
-
2037
- ### Registering plugins globally
2038
-
2039
- Plugins registered on `globalPluginRegistry` are automatically seeded into every logger created after the call.
2040
-
2041
- ```typescript
2042
- import { usePlugin } from 'logixia';
2043
-
2044
- usePlugin(myPlugin);
2045
-
2046
- // All loggers created from this point forward will run myPlugin.
2047
- const logger = createLogger({/* ... */});
2048
- ```
2049
-
2050
- ### Per-logger plugins
2051
-
2052
- Register or remove plugins on a specific logger instance at any time:
2053
-
2054
- ```typescript
2055
- const logger = createLogger({ context: 'PaymentService' });
2056
-
2057
- // Register
2058
- logger.use(myPlugin);
2059
-
2060
- // Deregister by name
2061
- logger.unuse('my-plugin');
2062
- ```
2063
-
2064
- `use()` is chainable:
2065
-
2066
- ```typescript
2067
- createLogger({ context: 'api' }).use(metricsPlugin).use(auditPlugin).use(samplerPlugin);
2068
- ```
2069
-
2070
- ### Cancelling a log entry
2071
-
2072
- Returning `null` from `onLog` drops the entry before it reaches any transport — useful for sampling, deduplication, or environment-based suppression:
2073
-
2074
- ```typescript
2075
- const devOnlyPlugin: LogixiaPlugin = {
2076
- name: 'dev-only',
2077
- onLog(entry) {
2078
- // Suppress debug/trace entries in production
2079
- if (process.env.NODE_ENV === 'production' && entry.level <= 20) {
2080
- return null; // drop it
2081
- }
2082
- return entry;
2083
- },
2084
- };
2085
- ```
2086
-
2087
- Multiple `onLog` hooks run in registration order. If any hook returns `null` the pipeline stops and no further hooks are called.
2088
-
2089
- ---
2090
-
2091
- ## Metrics → Prometheus
2092
-
2093
- `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.
2094
-
2095
- ```typescript
2096
- import { createMetricsPlugin } from 'logixia';
2097
- ```
2098
-
2099
- ### Quick start (counters)
2100
-
2101
- ```typescript
2102
- import { createLogger, createMetricsPlugin } from 'logixia';
2103
- import http from 'node:http';
2104
-
2105
- const metrics = createMetricsPlugin({
2106
- http_requests_total: {
2107
- type: 'counter',
2108
- help: 'Total HTTP requests processed',
2109
- // Which log fields become Prometheus labels
2110
- labels: ['method', 'status', 'route'],
2111
- },
2112
- auth_failures_total: {
2113
- type: 'counter',
2114
- help: 'Authentication failures',
2115
- labels: ['reason'],
2116
- },
2117
- });
2118
-
2119
- const logger = createLogger({ context: 'api' }).use(metrics);
2120
-
2121
- // Every log entry automatically increments the matching counter
2122
- await logger.info('request handled', { method: 'GET', status: 200, route: '/users' });
2123
- await logger.warn('auth failed', { reason: 'bad-token' });
2124
-
2125
- // Expose /metrics
2126
- http.createServer(metrics.httpHandler()).listen(9100);
2127
- ```
2128
-
2129
- The `/metrics` response looks like:
2130
-
2131
- ```
2132
- # HELP logixia_http_requests_total Total HTTP requests processed
2133
- # TYPE logixia_http_requests_total counter
2134
- logixia_http_requests_total{method="GET",status="200",route="/users"} 1
2135
-
2136
- # HELP logixia_auth_failures_total Authentication failures
2137
- # TYPE logixia_auth_failures_total counter
2138
- logixia_auth_failures_total{reason="bad-token"} 1
2139
- ```
2140
-
2141
- ### Histograms
2142
-
2143
- Histograms record the distribution of a numeric field extracted from log entries. Typical use: request latency in milliseconds.
2144
-
2145
- ```typescript
2146
- const metrics = createMetricsPlugin({
2147
- http_request_duration_ms: {
2148
- type: 'histogram',
2149
- help: 'HTTP request latency in milliseconds',
2150
- // The log field whose numeric value is recorded as the observation
2151
- valueField: 'durationMs',
2152
- labels: ['route', 'method'],
2153
- // Custom bucket boundaries (defaults: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000])
2154
- buckets: [10, 50, 100, 200, 500, 1000, 5000],
2155
- },
2156
- });
2157
-
2158
- await logger.info('request complete', { route: '/api/orders', method: 'POST', durationMs: 142 });
2159
- ```
2160
-
2161
- Prometheus output includes `_bucket`, `_sum`, and `_count` lines, compatible with `histogram_quantile()`:
2162
-
2163
- ```
2164
- # HELP logixia_http_request_duration_ms HTTP request latency in milliseconds
2165
- # TYPE logixia_http_request_duration_ms histogram
2166
- logixia_http_request_duration_ms_bucket{le="10",route="/api/orders",method="POST"} 0
2167
- logixia_http_request_duration_ms_bucket{le="50",route="/api/orders",method="POST"} 0
2168
- logixia_http_request_duration_ms_bucket{le="100",route="/api/orders",method="POST"} 0
2169
- logixia_http_request_duration_ms_bucket{le="200",route="/api/orders",method="POST"} 1
2170
- ...
2171
- logixia_http_request_duration_ms_bucket{le="+Inf",route="/api/orders",method="POST"} 1
2172
- logixia_http_request_duration_ms_sum{route="/api/orders",method="POST"} 142
2173
- logixia_http_request_duration_ms_count{route="/api/orders",method="POST"} 1
2174
- ```
2175
-
2176
- ### Gauges
2177
-
2178
- Gauges track the current value of a numeric field — useful for queue depths, active connections, cache sizes:
2179
-
2180
- ```typescript
2181
- const metrics = createMetricsPlugin({
2182
- queue_depth: {
2183
- type: 'gauge',
2184
- help: 'Current number of items in the processing queue',
2185
- valueField: 'depth',
2186
- labels: ['queue'],
2187
- },
2188
- });
2189
-
2190
- await logger.info('queue snapshot', { queue: 'email', depth: 47 });
2191
- ```
2192
-
2193
- ### Exposing the /metrics endpoint
2194
-
2195
- **Plain Node.js `http` module:**
2196
-
2197
- ```typescript
2198
- import http from 'node:http';
2199
-
2200
- http.createServer(metrics.httpHandler()).listen(9100);
2201
- // GET http://localhost:9100/ → Prometheus text format
2202
- ```
2203
-
2204
- **Express:**
2205
-
2206
- ```typescript
2207
- import express from 'express';
2208
-
2209
- const app = express();
2210
- app.get('/metrics', metrics.expressHandler());
2211
- app.listen(3000);
2212
- ```
2213
-
2214
- **Manual render (any framework):**
2215
-
2216
- ```typescript
2217
- // Returns the full Prometheus text string
2218
- const text = metrics.render();
2219
- res.setHeader('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
2220
- res.end(text);
2221
- ```
2222
-
2223
- **Reset all counters** (e.g., between tests):
2224
-
2225
- ```typescript
2226
- metrics.reset();
2227
- ```
2228
-
2229
- ### Metric configuration reference
2230
-
2231
- ```typescript
2232
- interface CounterConfig {
2233
- type: 'counter';
2234
- help?: string; // # HELP line in Prometheus output
2235
- labels?: string[]; // Log entry fields to use as label keys
2236
- }
2237
-
2238
- interface HistogramConfig {
2239
- type: 'histogram';
2240
- help?: string;
2241
- valueField: string; // The log field holding the numeric observation
2242
- labels?: string[];
2243
- buckets?: number[]; // Upper-inclusive bucket boundaries (ms or any unit)
2244
- }
2245
-
2246
- interface GaugeConfig {
2247
- type: 'gauge';
2248
- help?: string;
2249
- valueField: string; // The log field whose value sets the gauge
2250
- labels?: string[];
2251
- }
2252
-
2253
- // Map of Prometheus metric name → config
2254
- type MetricsMap = Record<string, CounterConfig | HistogramConfig | GaugeConfig>;
2255
- ```
2256
-
2257
- 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.
2258
-
2259
- ---
2260
-
2261
- ## Logger instance API
2262
-
2263
- Complete reference for every method available on a logger instance returned by `createLogger` or `LogixiaLoggerService`:
2264
-
2265
- ```typescript
2266
- // Log methods
2267
- await logger.error(message: string | Error, data?: Record<string, unknown>): Promise<void>
2268
- await logger.warn(message: string, data?: Record<string, unknown>): Promise<void>
2269
- await logger.info(message: string, data?: Record<string, unknown>): Promise<void>
2270
- await logger.debug(message: string, data?: Record<string, unknown>): Promise<void>
2271
- await logger.trace(message: string, data?: Record<string, unknown>): Promise<void>
2272
- await logger.verbose(message: string, data?: Record<string, unknown>): Promise<void>
2273
- await logger.logLevel(level: string, message: string, data?): Promise<void> // dynamic dispatch
2274
-
2275
- // Timer API
2276
- logger.time(label: string): void
2277
- await logger.timeEnd(label: string): Promise<number | undefined> // returns ms
2278
- await logger.timeAsync<T>(label: string, fn: () => Promise<T>): Promise<T>
2279
-
2280
- // Level management
2281
- logger.setLevel(level: string): void
2282
- logger.getLevel(): string
2283
-
2284
- // Context management
2285
- logger.setContext(context: string): void
2286
- logger.getContext(): string | undefined
2287
- logger.child(context: string, data?: Record<string, unknown>): ILogger
2288
-
2289
- // Field management
2290
- logger.enableField(fieldName: string): void
2291
- logger.disableField(fieldName: string): void
2292
- logger.isFieldEnabled(fieldName: string): boolean
2293
- logger.getFieldState(): Record<string, boolean>
2294
- logger.resetFieldState(): void
2295
-
2296
- // Transport management
2297
- logger.getAvailableTransports(): string[]
2298
- logger.setTransportLevels(transportId: string, levels: string[]): void
2299
- logger.getTransportLevels(transportId: string): string[] | undefined
2300
- logger.clearTransportLevelPreferences(): void
2301
-
2302
- // Plugin API
2303
- logger.use(plugin: LogixiaPlugin): this // register a plugin; chainable
2304
- logger.unuse(pluginName: string): this // remove a plugin by name; chainable
2305
-
2306
- // Lifecycle
2307
- await logger.flush(): Promise<void>
2308
- await logger.close(): Promise<void>
2309
- await logger.healthCheck(): Promise<{ healthy: boolean; details: Record<string, unknown> }>
2310
- ```
2311
-
2312
- **Utility exports** available at the top level:
2313
-
2314
- ```typescript
2315
- import {
2316
- generateTraceId, // () => string — UUID v4
2317
- getCurrentTraceId, // () => string | undefined
2318
- runWithTraceId, // (id, fn, data?) => T
2319
- setTraceId, // (id, data?) => void
2320
- extractTraceId, // (req, config) => string | undefined
2321
- isError, // (value) => value is Error
2322
- normalizeError, // (value) => Error
2323
- serializeError, // (error, options?) => Record<string, unknown>
2324
- applyRedaction, // (payload, config) => payload
2325
- flushOnExit, // (options?) => void
2326
- registerForShutdown, // (logger) => void
2327
- deregisterFromShutdown, // (logger) => void
2328
- resetShutdownHandlers, // () => void — useful in tests
2329
- } from 'logixia';
2330
-
2331
- // NestJS extras (requires @nestjs/common):
2332
- import {
2333
- InjectLogger, // @InjectLogger() parameter decorator
2334
- LogMethod, // @LogMethod() method decorator
2335
- LogixiaExceptionFilter, // global exception filter
2336
- } from 'logixia/nest';
2337
-
2338
- // Cloud transports (subpath imports):
2339
- import { CloudWatchTransport } from 'logixia/cloudwatch';
2340
- import { GCPTransport } from 'logixia/gcp';
2341
- import { AzureMonitorTransport } from 'logixia/azure';
2342
-
2343
- // Plugin API:
2344
- import type { LogixiaPlugin } from 'logixia';
2345
- import { globalPluginRegistry, PluginRegistry, usePlugin } from 'logixia';
2346
-
2347
- // Metrics → Prometheus:
2348
- import type {
2349
- CounterConfig,
2350
- GaugeConfig,
2351
- HistogramConfig,
2352
- MetricConfig,
2353
- MetricsMap,
2354
- } from 'logixia';
2355
- import { createMetricsPlugin, MetricsPlugin } from 'logixia';
2356
-
2357
- // Correlation ID sub-package:
2358
- import { correlationMiddleware, correlationFetch, withCorrelationId } from 'logixia/correlation';
2359
-
2360
- // Browser / Edge sub-package:
2361
- import {
2362
- createBrowserLogger,
2363
- BrowserConsoleTransport,
2364
- BrowserRemoteTransport,
2365
- } from 'logixia/browser';
2366
- ```
2367
-
2368
- ---
2369
-
2370
- ## CLI tool
2371
-
2372
- logixia ships a CLI for working with log files directly. After installing, the `logixia` command is available via `npx` or globally:
2373
-
2374
- ```bash
2375
- npx logixia --help
2376
- ```
2377
-
2378
- Seven subcommands are available:
2379
-
2380
- | Command | One-line summary |
2381
- | --------- | ------------------------------------------------------------ |
2382
- | `tail` | Stream a log file in real-time with level highlighting |
2383
- | `search` | Full-text or field-specific search with table / JSON output |
2384
- | `stats` | Level counts and time distribution summary |
2385
- | `analyze` | Pattern recognition and anomaly detection |
2386
- | `export` | Convert between NDJSON, JSON, and CSV |
2387
- | `query` | SQL-like queries with aggregations, sorting, and live follow |
2388
- | `explore` | Full-screen interactive TUI browser with search and export |
2389
-
2390
- **`tail`** — stream a log file in real-time, with optional filtering and level highlighting:
2391
-
2392
- ```bash
2393
- # Show last 10 lines
2394
- npx logixia tail ./logs/app.log
2395
-
2396
- # Follow and filter by level
2397
- npx logixia tail ./logs/app.log --follow --filter level:error
2398
-
2399
- # Filter by a specific field value
2400
- npx logixia tail ./logs/app.log --follow --filter user_id:usr_123
2401
-
2402
- # Color output by log level
2403
- npx logixia tail ./logs/app.log --highlight level
2404
- ```
2405
-
2406
- **`search`** — query a log file with field-specific or full-text search:
2407
-
2408
- ```bash
2409
- # Full-text search
2410
- npx logixia search ./logs/app.log --query "payment failed"
2411
-
2412
- # Field-specific search
2413
- npx logixia search ./logs/app.log --query "level:error"
2414
- npx logixia search ./logs/app.log --query "user_id:usr_123"
2415
-
2416
- # Output as JSON or table
2417
- npx logixia search ./logs/app.log --query "timeout" --format json
2418
- npx logixia search ./logs/app.log --query "timeout" --format table
2419
- ```
2420
-
2421
- **`stats`** — summarize a log file with counts by level and time distribution:
2422
-
2423
- ```bash
2424
- npx logixia stats ./logs/app.log
2425
- ```
2426
-
2427
- **`analyze`** — run pattern recognition and anomaly detection across a log file:
2428
-
2429
- ```bash
2430
- npx logixia analyze ./logs/app.log
2431
- ```
2432
-
2433
- **`export`** — convert a log file between formats (JSON, CSV, text):
2434
-
2435
- ```bash
2436
- npx logixia export ./logs/app.log --format csv --output ./logs/app.csv
2437
- ```
2438
-
2439
- **`query`** — run SQL-like queries over NDJSON / JSON log files directly in your terminal:
2440
-
2441
- ```bash
2442
- # Basic filter
2443
- npx logixia query ./logs/app.log --sql "SELECT * FROM logs WHERE level='error'"
2444
-
2445
- # Multiple conditions
2446
- npx logixia query ./logs/app.log --sql "WHERE level='error' AND duration > 500"
2447
-
2448
- # Select specific fields
2449
- npx logixia query ./logs/app.log --sql "SELECT level, message, duration FROM logs WHERE level='warn'"
2450
-
2451
- # Time-range shortcuts
2452
- npx logixia query ./logs/app.log --since "last 2 hours"
2453
- npx logixia query ./logs/app.log --since "last 30 minutes" --until "last 5 minutes"
2454
- npx logixia query ./logs/app.log --since today
2455
- npx logixia query ./logs/app.log --since yesterday
2456
-
2457
- # Aggregations
2458
- npx logixia query ./logs/app.log --sql "COUNT BY level"
2459
- npx logixia query ./logs/app.log --sql "GROUP BY statusCode"
2460
- npx logixia query ./logs/app.log --sql "AVG(duration) BY endpoint"
2461
- npx logixia query ./logs/app.log --sql "SUM(duration) BY service"
2462
-
2463
- # Sorting and limiting
2464
- npx logixia query ./logs/app.log --sql "WHERE level='error' ORDER BY timestamp DESC LIMIT 20"
2465
- npx logixia query ./logs/app.log --order-by duration --limit 10
2466
-
2467
- # Output formats
2468
- npx logixia query ./logs/app.log --sql "COUNT BY level" --format table
2469
- npx logixia query ./logs/app.log --sql "WHERE level='error'" --format json
2470
-
2471
- # Live tail with a SQL filter — streams new entries as they are written
2472
- npx logixia query ./logs/app.log --follow --sql "WHERE level='error'"
2473
- npx logixia query ./logs/app.log --follow --since "last 1 hour" --sql "WHERE duration > 1000"
2474
- ```
2475
-
2476
- Supported SQL features:
2477
-
2478
- | Clause / keyword | Example |
2479
- | ---------------------------- | ---------------------------------------- |
2480
- | `SELECT fields` | `SELECT level, message, duration` |
2481
- | `WHERE` conditions | `WHERE level='error' AND duration > 500` |
2482
- | Comparison ops | `= != > >= < <=` |
2483
- | `LIKE` / `NOT LIKE` | `WHERE message LIKE '%timeout%'` |
2484
- | `IN` / `NOT IN` | `WHERE level IN ('error', 'warn')` |
2485
- | `COUNT BY field` | `COUNT BY statusCode` |
2486
- | `GROUP BY field` | `GROUP BY endpoint` |
2487
- | `AVG(f) BY g` | `AVG(duration) BY endpoint` |
2488
- | `SUM / MIN / MAX(f) BY g` | `MAX(duration) BY service` |
2489
- | `ORDER BY field [ASC\|DESC]` | `ORDER BY timestamp DESC` |
2490
- | `LIMIT n` | `LIMIT 50` |
2491
-
2492
- `--since` / `--until` accept: `"last N minutes"`, `"last N hours"`, `"last N days"`, `"today"`, `"yesterday"`, or any ISO 8601 date string.
2493
-
2494
- ### explore — Visual TUI log explorer
2495
-
2496
- `logixia explore` opens a full-screen interactive terminal UI built on raw ANSI + chalk — no extra runtime dependencies required.
2497
-
2498
- ```bash
2499
- # Open a log file in the TUI
2500
- npx logixia explore ./logs/app.log
2501
-
2502
- # Start with only error and warn entries visible
2503
- npx logixia explore ./logs/app.log --levels error,warn
2504
-
2505
- # Pre-populate the search field
2506
- npx logixia explore ./logs/app.log --search "payment"
2507
-
2508
- # Follow mode: append new entries as the file grows
2509
- npx logixia explore ./logs/app.log --follow
2510
- ```
2511
-
2512
- **Options:**
2513
-
2514
- | Flag | Default | Description |
2515
- | ------------------ | ------- | --------------------------------------------------------------------- |
2516
- | `--follow` | off | Tail the file; append new entries as they are written |
2517
- | `--levels <list>` | all | Comma-separated levels to show: `error,warn,info,debug,trace,verbose` |
2518
- | `--search <query>` | — | Pre-populate the search field on open |
2519
-
2520
- **Layout:**
2521
-
2522
- ```
2523
- ┌────────────────────────────────────────────────────────────────────────────┐
2524
- │ LOGIXIA EXPLORE app.log [42/127] /payment │
2525
- │ E W I D T V /: search │
2526
- │ TIME LVL MESSAGE │
2527
- │ 08:00:01.042 ERR Request failed status=500 user=abc │
2528
- │▶08:00:02.117 INF Request completed status=200 user=def ← selected │
2529
- │ 08:00:03.890 WRN Slow query detected duration=1240 │
2530
- │──────────────────────────────────────────────────────────────── ▼ DETAIL ─│
2531
- │ { │
2532
- │ "timestamp": "...", │
2533
- │ "level": "info", │
2534
- │ "message": "Request completed", │
2535
- │ "status": 200, │
2536
- │ "user": "def" │
2537
- │ } │
2538
- │ j/k move /search x export E/W/I/D/T/V filter J/K detail↕ q quit │
2539
- └────────────────────────────────────────────────────────────────────────────┘
2540
- ```
2541
-
2542
- **Keyboard shortcuts:**
2543
-
2544
- | Key | Action |
2545
- | --------------- | ------------------------------------------------------------------------------ |
2546
- | `j` / `↓` | Move selection down |
2547
- | `k` / `↑` | Move selection up |
2548
- | `g` / `Home` | Jump to first entry |
2549
- | `G` / `End` | Jump to last entry |
2550
- | `PgUp` / `PgDn` | Page up / page down |
2551
- | `J` / `K` | Scroll detail panel down / up |
2552
- | `/` | Enter search mode (type query → `Enter` to confirm, `Esc` to clear) |
2553
- | `E W I D T V` | Toggle error / warn / info / debug / trace / verbose filter |
2554
- | `f` | Toggle real-time follow mode |
2555
- | `x` | Export filtered entries (enter a path ending in `.json`, `.csv`, or `.ndjson`) |
2556
- | `q` / `Ctrl+C` | Quit and return to terminal |
2557
-
2558
- **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.
2559
-
2560
- **Export** supports three formats determined by the file extension you type:
2561
-
2562
- - `.json` — pretty-printed JSON array
2563
- - `.csv` — comma-separated with auto-detected column headers
2564
- - `.ndjson` / `.jsonl` — newline-delimited JSON (one entry per line)
2565
-
2566
- 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).
2567
-
2568
- ---
2569
-
2570
- ## Configuration reference
2571
-
2572
- ```typescript
2573
- interface LoggerConfig {
2574
- // Required
2575
- appName: string;
2576
- environment: 'development' | 'production';
2577
-
2578
- // Output format (applies to console and file text output)
2579
- format?: {
2580
- timestamp?: boolean; // include ISO timestamp. Default: true
2581
- colorize?: boolean; // ANSI color output. Default: true
2582
- json?: boolean; // JSON lines output. Default: false
2583
- };
2584
-
2585
- // Trace ID — true enables UUID v4 auto-generation; pass an object for custom config
2586
- traceId?:
2587
- | boolean
2588
- | {
2589
- enabled: boolean;
2590
- generator?: () => string; // custom ID generator
2591
- contextKey?: string;
2592
- extractor?: {
2593
- header?: string | string[]; // headers to check
2594
- query?: string | string[]; // query params to check
2595
- body?: string | string[]; // body fields to check
2596
- params?: string | string[]; // route params to check
2597
- };
2598
- };
2599
-
2600
- // Suppress all output. Useful in test environments
2601
- silent?: boolean;
2602
-
2603
- // Level configuration
2604
- levelOptions?: {
2605
- level?: 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'verbose' | string;
2606
- levels?: Record<string, number>; // custom level priority map
2607
- colors?: Record<string, LogColor>; // color per level
2608
- };
2609
-
2610
- // Visible fields in text output
2611
- fields?: Partial<
2612
- Record<
2613
- | 'timestamp'
2614
- | 'level'
2615
- | 'appName'
2616
- | 'service'
2617
- | 'traceId'
2618
- | 'message'
2619
- | 'payload'
2620
- | 'timeTaken'
2621
- | 'context'
2622
- | 'requestId'
2623
- | 'userId'
2624
- | 'sessionId'
2625
- | 'environment',
2626
- string | boolean
2627
- >
2628
- >;
2629
-
2630
- // Field redaction — applied before any transport receives the entry
2631
- redact?: {
2632
- paths?: string[]; // dot-notation paths; supports * and ** wildcards
2633
- patterns?: RegExp[]; // regex patterns applied to string values
2634
- censor?: string; // replacement string. Default: '[REDACTED]'
2635
- };
2636
-
2637
- // Per-namespace log level overrides; keys are patterns, values are levels
2638
- namespaceLevels?: Record<string, string>;
2639
-
2640
- // Graceful shutdown — true for defaults, or a config object for full control
2641
- gracefulShutdown?:
2642
- | boolean
2643
- | {
2644
- enabled: boolean;
2645
- timeout?: number; // ms to wait before force-exit. Default: 5000
2646
- signals?: NodeJS.Signals[]; // Default: ['SIGTERM', 'SIGINT']
2647
- };
2648
-
2649
- // Transports — all optional, all concurrent
2650
- transports?: {
2651
- console?: {
2652
- level?: string;
2653
- colorize?: boolean;
2654
- timestamp?: boolean;
2655
- format?: 'json' | 'text';
2656
- filter?: (entry: TransportLogEntry) => boolean;
2657
- };
2658
-
2659
- file?:
2660
- | {
2661
- filename: string;
2662
- dirname?: string;
2663
- maxSize?: string | number; // e.g. '50MB', '1GB', or bytes
2664
- maxFiles?: number;
2665
- datePattern?: string; // e.g. 'YYYY-MM-DD'
2666
- zippedArchive?: boolean;
2667
- format?: 'json' | 'text' | 'csv';
2668
- level?: string;
2669
- batchSize?: number;
2670
- flushInterval?: number; // ms
2671
- maxQueue?: number; // cap on unwritten entries held in memory. Default: no cap
2672
- queuePolicy?: 'drop-oldest' | 'drop-newest' | 'block'; // Default: 'drop-oldest'
2673
- rotation?: {
2674
- interval?: '1h' | '6h' | '12h' | '1d' | '1w' | '1m' | '1y';
2675
- maxSize?: string | number;
2676
- maxFiles?: number;
2677
- compress?: boolean;
2678
- shouldRotate?: (
2679
- currentFile: string,
2680
- stats: { size: number; created: Date; modified: Date }
2681
- ) => boolean;
2682
- };
2683
- filter?: (entry: TransportLogEntry) => boolean;
2684
- }
2685
- | Array<FileTransportConfig>; // array for multiple file targets
2686
-
2687
- database?:
2688
- | {
2689
- type: 'postgresql' | 'mysql' | 'mongodb' | 'sqlite';
2690
- host?: string;
2691
- port?: number;
2692
- database: string;
2693
- table?: string; // SQL databases
2694
- collection?: string; // MongoDB
2695
- connectionString?: string; // MongoDB connection string
2696
- username?: string;
2697
- password?: string;
2698
- ssl?: boolean;
2699
- level?: string; // minimum level threshold for this transport
2700
- batchSize?: number;
2701
- flushInterval?: number; // ms
2702
- filter?: (entry: TransportLogEntry) => boolean; // entry-level predicate
2703
- }
2704
- | Array<DatabaseTransportConfig>;
2705
-
2706
- analytics?: {
2707
- datadog?: {
2708
- apiKey: string;
2709
- site?: 'datadoghq.com' | 'datadoghq.eu' | 'us3.datadoghq.com' | 'us5.datadoghq.com';
2710
- service?: string;
2711
- version?: string;
2712
- env?: string;
2713
- enableMetrics?: boolean;
2714
- enableLogs?: boolean;
2715
- enableTraces?: boolean;
2716
- };
2717
- mixpanel?: {
2718
- token: string;
2719
- distinct_id?: string;
2720
- enableSuperProperties?: boolean;
2721
- superProperties?: Record<string, unknown>;
2722
- };
2723
- segment?: {
2724
- writeKey: string;
2725
- dataPlaneUrl?: string;
2726
- enableBatching?: boolean;
2727
- flushAt?: number;
2728
- flushInterval?: number;
2729
- };
2730
- googleAnalytics?: {
2731
- measurementId: string;
2732
- apiSecret: string;
2733
- clientId?: string;
2734
- enableEcommerce?: boolean;
2735
- };
2736
- };
2737
-
2738
- custom?: ITransport[]; // any object implementing { name, write, close? }
2739
- };
2740
- }
2741
- ```
2742
-
2743
- ---
2744
-
2745
- ## Sponsor
2746
-
2747
- logixia is free and MIT-licensed, and it is built and maintained by one person. If it saves you time, sponsoring keeps it moving:
2748
-
2749
- - [GitHub Sponsors](https://github.com/sponsors/webcoderspeed)
2750
- - [PayPal](https://paypal.me/Sniperspeed)
322
+ - [GitHub Sponsors](https://github.com/sponsors/webcoderspeed)
323
+ - [PayPal](https://paypal.me/Sniperspeed)
2751
324
 
2752
325
  `npm fund logixia` prints the same links.
2753
326
 
2754
- ---
2755
-
2756
- ## Contributing
2757
-
2758
- ```bash
2759
- git clone https://github.com/Logixia/logixia.git
2760
- cd logixia
2761
- npm install
2762
- npm test
2763
- ```
2764
-
2765
- Pull requests are welcome. For significant changes, please open an issue first to discuss what you'd like to change.
2766
-
2767
- ---
2768
-
2769
327
  ## License
2770
328
 
2771
- [MIT](https://opensource.org/licenses/MIT) © [Sanjeev Sharma](https://github.com/webcoderspeed)
329
+ [MIT](./LICENSE) © [Sanjeev Sharma](https://github.com/webcoderspeed)