logixia 2.0.0 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +172 -2597
  2. package/dist/async-context-2mbUKhcl.mjs +132 -0
  3. package/dist/async-context-2mbUKhcl.mjs.map +1 -0
  4. package/dist/async-context-DUFMDYAr.js +148 -0
  5. package/dist/async-context-DUFMDYAr.js.map +1 -0
  6. package/dist/{azure-monitor.transport-CrvfHd9C.js → azure-monitor.transport-Bu4S2I2q.js} +32 -20
  7. package/dist/azure-monitor.transport-Bu4S2I2q.js.map +1 -0
  8. package/dist/{azure-monitor.transport-Cn_3Usvu.d.ts → azure-monitor.transport-Bvf7Xegt.d.ts} +3 -6
  9. package/dist/azure-monitor.transport-Bvf7Xegt.d.ts.map +1 -0
  10. package/dist/{azure-monitor.transport-CHnjVRF-.mjs → azure-monitor.transport-C1RWAumT.mjs} +27 -15
  11. package/dist/azure-monitor.transport-C1RWAumT.mjs.map +1 -0
  12. package/dist/{azure-monitor.transport-D7Da4RAb.d.mts → azure-monitor.transport-CbSHOnNX.d.mts} +3 -6
  13. package/dist/azure-monitor.transport-CbSHOnNX.d.mts.map +1 -0
  14. package/dist/azure.d.mts +1 -2
  15. package/dist/azure.d.ts +1 -2
  16. package/dist/azure.js +3 -6
  17. package/dist/azure.mjs +1 -6
  18. package/dist/{batch-buffer-PGvSgbmd.mjs → batch-buffer-BvXLDPlV.mjs} +21 -7
  19. package/dist/batch-buffer-BvXLDPlV.mjs.map +1 -0
  20. package/dist/{batch-buffer-BY-mRWIK.js → batch-buffer-C7Am4Mw-.js} +53 -40
  21. package/dist/batch-buffer-C7Am4Mw-.js.map +1 -0
  22. package/dist/browser.d.mts +47 -27
  23. package/dist/browser.d.mts.map +1 -1
  24. package/dist/browser.d.ts +47 -27
  25. package/dist/browser.d.ts.map +1 -1
  26. package/dist/browser.js +464 -62
  27. package/dist/browser.js.map +1 -1
  28. package/dist/browser.mjs +465 -65
  29. package/dist/browser.mjs.map +1 -1
  30. package/dist/{builder-0gEzRopq.mjs → builder-DhNhvOkt.mjs} +307 -246
  31. package/dist/builder-DhNhvOkt.mjs.map +1 -0
  32. package/dist/{builder-D44QktIo.js → builder-yFvJrz7o.js} +435 -399
  33. package/dist/builder-yFvJrz7o.js.map +1 -0
  34. package/dist/cli/index.js +216 -125
  35. package/dist/cloudwatch.d.mts +1 -2
  36. package/dist/cloudwatch.d.ts +1 -2
  37. package/dist/cloudwatch.js +3 -7
  38. package/dist/cloudwatch.mjs +1 -7
  39. package/dist/{cloudwatch.transport-DsEq633o.d.ts → cloudwatch.transport-CQyT6U91.d.ts} +2 -6
  40. package/dist/cloudwatch.transport-CQyT6U91.d.ts.map +1 -0
  41. package/dist/{cloudwatch.transport-BO1ahxw-.js → cloudwatch.transport-Ch1Uae-0.js} +61 -36
  42. package/dist/cloudwatch.transport-Ch1Uae-0.js.map +1 -0
  43. package/dist/{cloudwatch.transport-UlyZFWMn.d.mts → cloudwatch.transport-CjVrI6S4.d.mts} +2 -6
  44. package/dist/cloudwatch.transport-CjVrI6S4.d.mts.map +1 -0
  45. package/dist/{cloudwatch.transport-DZ6ies7g.mjs → cloudwatch.transport-DZqF2xZO.mjs} +54 -28
  46. package/dist/cloudwatch.transport-DZqF2xZO.mjs.map +1 -0
  47. package/dist/{coerce.utils-Cu6IUTgX.mjs → coerce.utils-B_rEudCn.mjs} +3 -4
  48. package/dist/{coerce.utils-Cu6IUTgX.mjs.map → coerce.utils-B_rEudCn.mjs.map} +1 -1
  49. package/dist/{coerce.utils-B-LilukN.js → coerce.utils-C1C5sIX0.js} +8 -10
  50. package/dist/{coerce.utils-B-LilukN.js.map → coerce.utils-C1C5sIX0.js.map} +1 -1
  51. package/dist/correlation.d.mts +15 -69
  52. package/dist/correlation.d.mts.map +1 -1
  53. package/dist/correlation.d.ts +15 -69
  54. package/dist/correlation.d.ts.map +1 -1
  55. package/dist/correlation.js +69 -20
  56. package/dist/correlation.js.map +1 -1
  57. package/dist/correlation.mjs +67 -17
  58. package/dist/correlation.mjs.map +1 -1
  59. package/dist/{entry-fields-BvOLFlJ4.js → entry-fields-BSLkzw18.js} +16 -13
  60. package/dist/entry-fields-BSLkzw18.js.map +1 -0
  61. package/dist/{entry-fields-BJkuPnIg.mjs → entry-fields-J1OBi24o.mjs} +11 -7
  62. package/dist/entry-fields-J1OBi24o.mjs.map +1 -0
  63. package/dist/gcp.d.mts +1 -2
  64. package/dist/gcp.d.ts +1 -2
  65. package/dist/gcp.js +3 -7
  66. package/dist/gcp.mjs +1 -7
  67. package/dist/{gcp.transport-B7oc97sG.d.ts → gcp.transport-BCIvdFV-.d.ts} +5 -8
  68. package/dist/gcp.transport-BCIvdFV-.d.ts.map +1 -0
  69. package/dist/{gcp.transport-GrJurKo8.d.mts → gcp.transport-D9SdLPwP.d.mts} +5 -8
  70. package/dist/gcp.transport-D9SdLPwP.d.mts.map +1 -0
  71. package/dist/{gcp.transport-DBX5TPIy.mjs → gcp.transport-DVCmnA3b.mjs} +59 -16
  72. package/dist/gcp.transport-DVCmnA3b.mjs.map +1 -0
  73. package/dist/{gcp.transport-CHV8MGgz.js → gcp.transport-Dy2Lf8FI.js} +64 -22
  74. package/dist/gcp.transport-Dy2Lf8FI.js.map +1 -0
  75. package/dist/http-shapes-BnBkJcid.d.mts +32 -0
  76. package/dist/http-shapes-BnBkJcid.d.mts.map +1 -0
  77. package/dist/http-shapes-BnBkJcid.d.ts +32 -0
  78. package/dist/http-shapes-BnBkJcid.d.ts.map +1 -0
  79. package/dist/{index-3mw5gaCw.d.mts → index-BSTtFcQ6.d.mts} +142 -13
  80. package/dist/index-BSTtFcQ6.d.mts.map +1 -0
  81. package/dist/{index-DhIoploR.d.ts → index-CFDGkI8n.d.ts} +142 -13
  82. package/dist/index-CFDGkI8n.d.ts.map +1 -0
  83. package/dist/index.d.mts +114 -217
  84. package/dist/index.d.mts.map +1 -1
  85. package/dist/index.d.ts +113 -216
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +182 -279
  88. package/dist/index.js.map +1 -1
  89. package/dist/index.mjs +160 -256
  90. package/dist/index.mjs.map +1 -1
  91. package/dist/internal-log-Bg4V1cPg.mjs +170 -0
  92. package/dist/internal-log-Bg4V1cPg.mjs.map +1 -0
  93. package/dist/internal-log-By4Govmu.js +223 -0
  94. package/dist/internal-log-By4Govmu.js.map +1 -0
  95. package/dist/{logitron-logger-UVeN-mQm.d.ts → logitron-logger-D22Yb61K.d.mts} +121 -100
  96. package/dist/logitron-logger-D22Yb61K.d.mts.map +1 -0
  97. package/dist/{logitron-logger-BFDSO8Io.d.mts → logitron-logger-D_MYYrQt.d.ts} +121 -100
  98. package/dist/logitron-logger-D_MYYrQt.d.ts.map +1 -0
  99. package/dist/middleware.d.mts +13 -28
  100. package/dist/middleware.d.mts.map +1 -1
  101. package/dist/middleware.d.ts +13 -28
  102. package/dist/middleware.d.ts.map +1 -1
  103. package/dist/middleware.js +20 -81
  104. package/dist/middleware.js.map +1 -1
  105. package/dist/middleware.mjs +17 -79
  106. package/dist/middleware.mjs.map +1 -1
  107. package/dist/nest.d.mts +37 -48
  108. package/dist/nest.d.mts.map +1 -1
  109. package/dist/nest.d.ts +37 -48
  110. package/dist/nest.d.ts.map +1 -1
  111. package/dist/nest.js +124 -135
  112. package/dist/nest.js.map +1 -1
  113. package/dist/nest.mjs +69 -78
  114. package/dist/nest.mjs.map +1 -1
  115. package/dist/{transport.manager-BiuCAJd2.mjs → otel-Dt5riXuE.mjs} +275 -117
  116. package/dist/otel-Dt5riXuE.mjs.map +1 -0
  117. package/dist/{transport.manager-CXbKKdVE.js → otel-DyeH6DVR.js} +373 -178
  118. package/dist/otel-DyeH6DVR.js.map +1 -0
  119. package/dist/own-property-BWkibepN.js +40 -0
  120. package/dist/own-property-BWkibepN.js.map +1 -0
  121. package/dist/own-property-CjFw9qwZ.mjs +29 -0
  122. package/dist/own-property-CjFw9qwZ.mjs.map +1 -0
  123. package/dist/{trace.utils-CvXgaiQT.mjs → request-trace-BgNykH9M.mjs} +40 -9
  124. package/dist/request-trace-BgNykH9M.mjs.map +1 -0
  125. package/dist/{trace.utils-DpBOLDtq.js → request-trace-ByujsUZk.js} +117 -77
  126. package/dist/request-trace-ByujsUZk.js.map +1 -0
  127. package/dist/rolldown-runtime-D3PmRqjj.mjs +4 -0
  128. package/dist/{safe-stringify-DJ66tW5I.mjs → safe-stringify-B5p5ktFe.mjs} +39 -15
  129. package/dist/safe-stringify-B5p5ktFe.mjs.map +1 -0
  130. package/dist/{safe-stringify-DWo3aZp_.js → safe-stringify-J3sh-7YM.js} +59 -36
  131. package/dist/safe-stringify-J3sh-7YM.js.map +1 -0
  132. package/dist/{search-C4V0OMEn.js → search-CRjhHxe9.js} +319 -92
  133. package/dist/search-CRjhHxe9.js.map +1 -0
  134. package/dist/{search-CNh9uVwR.d.ts → search-DA3ft3X5.d.mts} +5 -22
  135. package/dist/search-DA3ft3X5.d.mts.map +1 -0
  136. package/dist/{search-BFbu_oVn.mjs → search-DAXKE-P9.mjs} +289 -62
  137. package/dist/search-DAXKE-P9.mjs.map +1 -0
  138. package/dist/{search-DXANsQ2n.d.mts → search-DEl3Pe47.d.ts} +5 -22
  139. package/dist/search-DEl3Pe47.d.ts.map +1 -0
  140. package/dist/search.d.mts +2 -4
  141. package/dist/search.d.ts +2 -4
  142. package/dist/search.js +3 -4
  143. package/dist/search.mjs +1 -4
  144. package/dist/secret-config-CmK2wVkr.js +272 -0
  145. package/dist/secret-config-CmK2wVkr.js.map +1 -0
  146. package/dist/secret-config-Dhb6pqTU.mjs +210 -0
  147. package/dist/secret-config-Dhb6pqTU.mjs.map +1 -0
  148. package/dist/{slack.transport-CXjQYOes.d.ts → slack.transport-BDyENuR0.d.mts} +56 -9
  149. package/dist/slack.transport-BDyENuR0.d.mts.map +1 -0
  150. package/dist/{slack.transport-Bzw7sucI.mjs → slack.transport-BTEwsREY.mjs} +263 -31
  151. package/dist/slack.transport-BTEwsREY.mjs.map +1 -0
  152. package/dist/{slack.transport-BZzoXDPn.d.mts → slack.transport-C6E0l4kw.d.ts} +56 -9
  153. package/dist/slack.transport-C6E0l4kw.d.ts.map +1 -0
  154. package/dist/{slack.transport-BfJRouB2.js → slack.transport-UbYvOdKx.js} +286 -42
  155. package/dist/slack.transport-UbYvOdKx.js.map +1 -0
  156. package/dist/testing.d.mts +13 -11
  157. package/dist/testing.d.mts.map +1 -1
  158. package/dist/testing.d.ts +13 -11
  159. package/dist/testing.d.ts.map +1 -1
  160. package/dist/testing.js +41 -7
  161. package/dist/testing.js.map +1 -1
  162. package/dist/testing.mjs +40 -6
  163. package/dist/testing.mjs.map +1 -1
  164. package/dist/{transport.types-CGoHpWQR.d.ts → transport.types-CzJq4IrF.d.mts} +14 -5
  165. package/dist/transport.types-CzJq4IrF.d.mts.map +1 -0
  166. package/dist/{transport.types-BMnurPcc.d.mts → transport.types-CzJq4IrF.d.ts} +14 -5
  167. package/dist/transport.types-CzJq4IrF.d.ts.map +1 -0
  168. package/dist/transports.d.mts +39 -41
  169. package/dist/transports.d.mts.map +1 -1
  170. package/dist/transports.d.ts +39 -41
  171. package/dist/transports.d.ts.map +1 -1
  172. package/dist/transports.js +19 -22
  173. package/dist/transports.mjs +7 -13
  174. package/dist/worker-entry.js +438 -107
  175. package/dist/worker.d.mts +1 -2
  176. package/dist/worker.d.ts +1 -2
  177. package/dist/worker.js +3 -4
  178. package/dist/worker.mjs +1 -4
  179. package/dist/{worker.transport-JvGPog3C.d.mts → worker.transport-B9mCQSM1.d.ts} +7 -6
  180. package/dist/worker.transport-B9mCQSM1.d.ts.map +1 -0
  181. package/dist/{worker.transport-CdzSZJmw.d.ts → worker.transport-Bl8lFRck.d.mts} +7 -6
  182. package/dist/worker.transport-Bl8lFRck.d.mts.map +1 -0
  183. package/dist/{worker.transport-lPxO2MVL.mjs → worker.transport-BmmOKb1-.mjs} +43 -10
  184. package/dist/worker.transport-BmmOKb1-.mjs.map +1 -0
  185. package/dist/{worker.transport-BdPxu3PN.js → worker.transport-DxxbqQCG.js} +48 -15
  186. package/dist/worker.transport-DxxbqQCG.js.map +1 -0
  187. package/package.json +51 -75
  188. package/dist/async-context-BzJrbfQs.mjs +0 -72
  189. package/dist/async-context-BzJrbfQs.mjs.map +0 -1
  190. package/dist/async-context-DyQQYq3I.js +0 -92
  191. package/dist/async-context-DyQQYq3I.js.map +0 -1
  192. package/dist/azure-monitor.transport-CHnjVRF-.mjs.map +0 -1
  193. package/dist/azure-monitor.transport-Cn_3Usvu.d.ts.map +0 -1
  194. package/dist/azure-monitor.transport-CrvfHd9C.js.map +0 -1
  195. package/dist/azure-monitor.transport-D7Da4RAb.d.mts.map +0 -1
  196. package/dist/batch-buffer-BY-mRWIK.js.map +0 -1
  197. package/dist/batch-buffer-PGvSgbmd.mjs.map +0 -1
  198. package/dist/builder-0gEzRopq.mjs.map +0 -1
  199. package/dist/builder-D44QktIo.js.map +0 -1
  200. package/dist/chunk-DLSkYcPk.mjs +0 -7
  201. package/dist/chunk-nOFOJqeH.js +0 -30
  202. package/dist/cloudwatch.transport-BO1ahxw-.js.map +0 -1
  203. package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +0 -1
  204. package/dist/cloudwatch.transport-DsEq633o.d.ts.map +0 -1
  205. package/dist/cloudwatch.transport-UlyZFWMn.d.mts.map +0 -1
  206. package/dist/entry-fields-BJkuPnIg.mjs.map +0 -1
  207. package/dist/entry-fields-BvOLFlJ4.js.map +0 -1
  208. package/dist/gcp.transport-B7oc97sG.d.ts.map +0 -1
  209. package/dist/gcp.transport-CHV8MGgz.js.map +0 -1
  210. package/dist/gcp.transport-DBX5TPIy.mjs.map +0 -1
  211. package/dist/gcp.transport-GrJurKo8.d.mts.map +0 -1
  212. package/dist/index-3mw5gaCw.d.mts.map +0 -1
  213. package/dist/index-DhIoploR.d.ts.map +0 -1
  214. package/dist/internal-log-BUJYMZbE.js +0 -70
  215. package/dist/internal-log-BUJYMZbE.js.map +0 -1
  216. package/dist/internal-log-Cm8SOWSV.mjs +0 -52
  217. package/dist/internal-log-Cm8SOWSV.mjs.map +0 -1
  218. package/dist/logitron-logger-BFDSO8Io.d.mts.map +0 -1
  219. package/dist/logitron-logger-UVeN-mQm.d.ts.map +0 -1
  220. package/dist/safe-stringify-DJ66tW5I.mjs.map +0 -1
  221. package/dist/safe-stringify-DWo3aZp_.js.map +0 -1
  222. package/dist/search-BFbu_oVn.mjs.map +0 -1
  223. package/dist/search-C4V0OMEn.js.map +0 -1
  224. package/dist/search-CNh9uVwR.d.ts.map +0 -1
  225. package/dist/search-DXANsQ2n.d.mts.map +0 -1
  226. package/dist/slack.transport-BZzoXDPn.d.mts.map +0 -1
  227. package/dist/slack.transport-BfJRouB2.js.map +0 -1
  228. package/dist/slack.transport-Bzw7sucI.mjs.map +0 -1
  229. package/dist/slack.transport-CXjQYOes.d.ts.map +0 -1
  230. package/dist/trace.utils-CvXgaiQT.mjs.map +0 -1
  231. package/dist/trace.utils-DpBOLDtq.js.map +0 -1
  232. package/dist/transport.manager-BiuCAJd2.mjs.map +0 -1
  233. package/dist/transport.manager-CXbKKdVE.js.map +0 -1
  234. package/dist/transport.types-BMnurPcc.d.mts.map +0 -1
  235. package/dist/transport.types-CGoHpWQR.d.ts.map +0 -1
  236. package/dist/worker.transport-BdPxu3PN.js.map +0 -1
  237. package/dist/worker.transport-CdzSZJmw.d.ts.map +0 -1
  238. package/dist/worker.transport-JvGPog3C.d.mts.map +0 -1
  239. package/dist/worker.transport-lPxO2MVL.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","names":["out: Record<string, unknown>","internalError","fields: Record<string, unknown>","sanitizeIncomingId","stringifyForLog","items: unknown[]","json: unknown","record: Record<string, unknown>"],"sources":["../src/middleware/http-logger.ts"],"sourcesContent":["/**\n * HTTP request/response logging middleware — Morgan replacement.\n *\n * Fixes every documented Morgan bug:\n * - statusCode always captured correctly, even for requests > 20 s\n * - Logs request START (with traceId) and response FINISH (with duration)\n * - Captures errors before and after response\n * - Auto-redacts Authorization / Cookie / Set-Cookie headers\n * - Slow-request warnings\n * - Skip predicates for health-check routes / static assets\n *\n * @example Express\n * ```ts\n * import { createExpressMiddleware } from 'logixia/middleware';\n * app.use(createExpressMiddleware(logger));\n * ```\n *\n * @example Fastify\n * ```ts\n * import { createFastifyPlugin } from 'logixia/middleware';\n * await fastify.register(createFastifyPlugin(logger));\n * ```\n */\n\nimport type { IBaseLogger } from '../types';\nimport { internalError } from '../utils/internal-log';\nimport { stringifyForLog } from '../utils/safe-stringify';\nimport { sanitizeIncomingId } from '../utils/trace.utils';\n\n// ── Shared types ─────────────────────────────────────────────────────────────\n\nexport interface HttpLoggerOptions {\n /**\n * Skip logging for a request. Called before any I/O.\n * @example `skip: (req) => req.url === '/health'`\n */\n skip?: (req: IncomingRequest) => boolean;\n /**\n * Log request body (POST/PUT/PATCH). Capped at `bodyMaxBytes` (default: 4096).\n * Redaction still applies to the captured body.\n * Default: false.\n */\n logBody?: boolean;\n /** Max bytes of body to capture. Default: 4096. */\n bodyMaxBytes?: number;\n /**\n * Emit a WARN log when a request duration exceeds this threshold (ms).\n * Default: 1000.\n */\n slowRequestThresholdMs?: number;\n /**\n * Additional fields to include in every log entry.\n * @example `extraFields: (req) => ({ tenantId: req.headers['x-tenant-id'] })`\n */\n extraFields?: (req: IncomingRequest) => Record<string, unknown>;\n /**\n * Trace ID header. Default: 'x-trace-id'.\n * If the header is absent, a short random ID is generated.\n */\n traceIdHeader?: string;\n /**\n * Additional headers to redact from logged output. Merged with the defaults\n * (authorization, cookie, set-cookie, x-api-key, proxy-authorization,\n * x-auth-token, x-access-token, x-csrf-token, x-amz-security-token).\n */\n redactHeaders?: string[];\n /**\n * Additional query-string parameter names whose values are redacted from the\n * logged URL. Merged with the defaults (token, access_token, refresh_token,\n * code, password, secret, api_key, apikey, key, signature, sig, auth).\n */\n redactQueryParams?: string[];\n /**\n * Log level for request-start entries. Default: 'debug'.\n * Set to 'silent' to suppress request-start logs entirely.\n */\n requestLevel?: string;\n /** Log level for successful response entries. Default: 'info'. */\n responseLevel?: string;\n /** Log level for error responses (status ≥ 500). Default: 'error'. */\n errorLevel?: string;\n}\n\n// Minimal structural types so we don't need @types/express / fastify in core\nexport interface IncomingRequest {\n method?: string;\n url?: string;\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n socket?: { remoteAddress?: string };\n ip?: string;\n}\n\nexport interface OutgoingResponse {\n statusCode?: number;\n on?: (event: string, cb: () => void) => void;\n once?: (event: string, cb: () => void) => void;\n getHeader?: (name: string) => string | number | string[] | undefined;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nconst DEFAULT_REDACT_HEADERS = [\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'proxy-authorization',\n 'x-auth-token',\n 'x-access-token',\n 'x-csrf-token',\n 'x-amz-security-token',\n];\n\nconst DEFAULT_REDACT_QUERY_PARAMS = [\n 'token',\n 'access_token',\n 'refresh_token',\n 'code',\n 'password',\n 'secret',\n 'api_key',\n 'apikey',\n 'key',\n 'signature',\n 'sig',\n 'auth',\n];\n\nfunction buildNameSet(defaults: readonly string[], extra: readonly string[] | undefined) {\n return new Set([...defaults, ...(extra ?? [])].map((n) => n.toLowerCase()));\n}\n\nfunction safeDecode(value: string): string {\n try {\n return decodeURIComponent(value.replace(/\\+/g, ' '));\n } catch {\n return value;\n }\n}\n\n/** Replace the values of sensitive query parameters, leaving the rest verbatim. */\nfunction sanitizeUrl(url: string, redactParams: Set<string>): string {\n const q = url.indexOf('?');\n if (q === -1) return url;\n const hashAt = url.indexOf('#', q);\n const query = url.slice(q + 1, hashAt === -1 ? undefined : hashAt);\n const hash = hashAt === -1 ? '' : url.slice(hashAt);\n const cleaned = query\n .split('&')\n .map((pair) => {\n const eq = pair.indexOf('=');\n const rawKey = eq === -1 ? pair : pair.slice(0, eq);\n const decoded = safeDecode(rawKey);\n const bracket = decoded.indexOf('[');\n const name = (bracket === -1 ? decoded : decoded.slice(0, bracket)).toLowerCase();\n return redactParams.has(name) && eq !== -1 ? `${rawKey}=[REDACTED]` : pair;\n })\n .join('&');\n return `${url.slice(0, q)}?${cleaned}${hash}`;\n}\n\nfunction sanitizeHeaders(\n headers: Record<string, string | string[] | undefined> | undefined,\n redactSet: Set<string>\n): Record<string, unknown> {\n if (!headers) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(headers)) {\n if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;\n out[k] = redactSet.has(k.toLowerCase()) ? '[REDACTED]' : v;\n }\n return out;\n}\n\n/** Fire-and-forget a logger call; a failing log must never become an unhandled rejection. */\nfunction fire(result: unknown): void {\n if (result && typeof (result as Promise<unknown>).catch === 'function') {\n (result as Promise<unknown>).catch((error: unknown) =>\n internalError('http-logger: log call failed', error)\n );\n }\n}\n\nfunction shortId(): string {\n // eslint-disable-next-line sonarjs/pseudo-random -- non-security request ID\n return Math.random().toString(36).slice(2, 10);\n}\n\ninterface NameSets {\n headers: Set<string>;\n queryParams: Set<string>;\n}\n\nfunction resolveNameSets(options: HttpLoggerOptions): NameSets {\n return {\n headers: buildNameSet(DEFAULT_REDACT_HEADERS, options.redactHeaders),\n queryParams: buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams),\n };\n}\n\nfunction buildBaseFields(\n req: IncomingRequest,\n traceId: string,\n options: HttpLoggerOptions,\n { headers: redactSet, queryParams: paramSet }: NameSets\n): Record<string, unknown> {\n const fields: Record<string, unknown> = {\n traceId,\n method: req.method?.toUpperCase() ?? 'UNKNOWN',\n url: sanitizeUrl(req.url ?? '/', paramSet),\n ip: req.ip ?? req.socket?.remoteAddress ?? 'unknown',\n headers: sanitizeHeaders(req.headers, redactSet),\n };\n\n if (options.extraFields) {\n Object.assign(fields, options.extraFields(req));\n }\n\n return fields;\n}\n\n// ── Express middleware ────────────────────────────────────────────────────────\n\n/**\n * Create an Express / Connect compatible middleware that replaces Morgan.\n */\nexport function createExpressMiddleware(\n logger: IBaseLogger,\n options: HttpLoggerOptions = {}\n): (req: IncomingRequest, res: OutgoingResponse, next: () => void) => void {\n const {\n skip,\n logBody,\n bodyMaxBytes,\n traceIdHeader = 'x-trace-id',\n requestLevel = 'debug',\n responseLevel = 'info',\n errorLevel = 'error',\n slowRequestThresholdMs = 1000,\n } = options;\n const nameSets = resolveNameSets(options);\n const traceHeaderName = traceIdHeader.toLowerCase();\n\n return function logixiaHttpMiddleware(\n req: IncomingRequest,\n res: OutgoingResponse,\n next: () => void\n ): void {\n if (skip?.(req)) {\n next();\n return;\n }\n\n const traceId = sanitizeIncomingId(req.headers?.[traceHeaderName]) ?? shortId();\n const startMs = Date.now();\n const baseFields = buildBaseFields(req, traceId, options, nameSets);\n\n // Log request start\n if (requestLevel !== 'silent') {\n fire(\n logger.logLevel(requestLevel, 'request started', {\n ...baseFields,\n ...(logBody && req.body ? { body: truncateBody(req.body, bodyMaxBytes) } : {}),\n })\n );\n }\n\n // Hook into the response 'finish' event — fires after headers + body are sent.\n // This is what Morgan gets wrong for slow requests (it uses 'close' which may\n // fire before the status code is set on some Node versions).\n //\n // BOTH 'finish' and 'close' fire on a normal response, so guard against\n // logging the completion twice (which would duplicate the completed entry and\n // the slow-request warning).\n let completed = false;\n const onFinish = (): void => {\n if (completed) return;\n completed = true;\n\n const duration = Date.now() - startMs;\n const status = res.statusCode ?? 0;\n const level = status >= 500 ? errorLevel : responseLevel;\n\n fire(\n logger.logLevel(level, 'request completed', {\n ...baseFields,\n statusCode: status,\n duration,\n })\n );\n\n if (duration > slowRequestThresholdMs) {\n fire(\n logger.warn('slow request detected', {\n ...baseFields,\n statusCode: status,\n duration,\n threshold: slowRequestThresholdMs,\n })\n );\n }\n };\n\n res.once?.('finish', onFinish);\n // Fallback: also covers a client that disconnects before 'finish' fires. The\n // `completed` guard ensures a normal finish+close pair logs only once.\n res.once?.('close', onFinish);\n\n next();\n };\n}\n\n// ── Fastify plugin ─────────────────────────────────────────────────────────────\n\nexport interface FastifyInstance {\n addHook: (name: string, fn: (req: unknown, reply: unknown, done: () => void) => void) => void;\n}\n\n/**\n * Create a Fastify plugin (a function you pass to `fastify.register()`).\n *\n * @example\n * ```ts\n * await fastify.register(createFastifyPlugin(logger, { slowRequestThresholdMs: 500 }));\n * ```\n */\nexport function createFastifyPlugin(logger: IBaseLogger, options: HttpLoggerOptions = {}) {\n const {\n skip,\n traceIdHeader = 'x-trace-id',\n requestLevel = 'debug',\n responseLevel = 'info',\n errorLevel = 'error',\n slowRequestThresholdMs = 1000,\n } = options;\n const nameSets = resolveNameSets(options);\n const traceHeaderName = traceIdHeader.toLowerCase();\n\n function logixiaFastifyPlugin(fastify: FastifyInstance, _opts: unknown, done: () => void): void {\n fastify.addHook('onRequest', (request: unknown, _reply: unknown, hookDone: () => void) => {\n const req = request as IncomingRequest & { _logixiaStart?: number; _logixiaId?: string };\n if (skip?.(req)) {\n hookDone();\n return;\n }\n\n const traceId = sanitizeIncomingId(req.headers?.[traceHeaderName]) ?? shortId();\n req._logixiaStart = Date.now();\n req._logixiaId = traceId;\n\n if (requestLevel !== 'silent') {\n fire(\n logger.logLevel(\n requestLevel,\n 'request started',\n buildBaseFields(req, traceId, options, nameSets)\n )\n );\n }\n hookDone();\n });\n\n fastify.addHook('onResponse', (request: unknown, reply: unknown, hookDone: () => void) => {\n const req = request as IncomingRequest & { _logixiaStart?: number; _logixiaId?: string };\n if (req._logixiaStart === undefined) {\n hookDone();\n return;\n }\n\n const rep = reply as { statusCode?: number };\n const duration = Date.now() - req._logixiaStart;\n const status = rep.statusCode ?? 0;\n const traceId = req._logixiaId ?? shortId();\n const level = status >= 500 ? errorLevel : responseLevel;\n const baseFields = buildBaseFields(req, traceId, options, nameSets);\n\n fire(\n logger.logLevel(level, 'request completed', {\n ...baseFields,\n statusCode: status,\n duration,\n })\n );\n\n if (duration > slowRequestThresholdMs) {\n fire(\n logger.warn('slow request detected', {\n traceId,\n url: baseFields.url,\n method: req.method,\n duration,\n threshold: slowRequestThresholdMs,\n })\n );\n }\n\n hookDone();\n });\n\n done();\n }\n\n return Object.assign(logixiaFastifyPlugin, {\n [Symbol.for('skip-override')]: true,\n [Symbol.for('fastify.display-name')]: 'logixia',\n });\n}\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\n/**\n * Bound the captured body without turning it into a string, so the logger's\n * path-based redaction still sees the original keys. Oversized structures are\n * pruned (extra keys/items dropped, long strings cut) rather than serialized.\n */\nfunction truncateBody(body: unknown, maxBytes = 4096): unknown {\n if (typeof body === 'string') {\n return body.length > maxBytes ? body.slice(0, maxBytes) + '…[truncated]' : body;\n }\n if (body && typeof body === 'object') {\n const str = stringifyForLog(body);\n if (str.length <= maxBytes) return body;\n return pruneValue(body, { left: maxBytes }, 0, new Set());\n }\n return body;\n}\n\nconst TRUNCATED = '…[truncated]';\nconst MAX_PRUNE_DEPTH = 8;\n\nfunction pruneValue(\n value: unknown,\n budget: { left: number },\n depth: number,\n ancestors: Set<object>\n): unknown {\n if (typeof value === 'string') {\n const cut = value.slice(0, Math.max(0, budget.left));\n budget.left -= cut.length;\n return cut.length < value.length ? cut + TRUNCATED : cut;\n }\n if (value === null || typeof value !== 'object') {\n budget.left -= 8;\n return value;\n }\n if (value instanceof Date) {\n budget.left -= 24;\n return Number.isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString();\n }\n if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {\n budget.left -= 16;\n return `[Buffer ${value.byteLength} bytes]`;\n }\n if (value instanceof Error) return value;\n if (ancestors.has(value) || depth >= MAX_PRUNE_DEPTH) return TRUNCATED;\n ancestors.add(value);\n try {\n if (Array.isArray(value) || value instanceof Set || value instanceof Map) {\n const items: unknown[] = [];\n for (const item of value instanceof Map ? value.entries() : value) {\n if (budget.left <= 0) {\n items.push(TRUNCATED);\n break;\n }\n items.push(pruneValue(item, budget, depth + 1, ancestors));\n }\n return items;\n }\n const toJSON = (value as { toJSON?: unknown }).toJSON;\n if (typeof toJSON === 'function') {\n const json: unknown = toJSON.call(value);\n if (json !== value) return pruneValue(json, budget, depth + 1, ancestors);\n }\n const record: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;\n if (budget.left <= 0) {\n record['…'] = TRUNCATED;\n break;\n }\n budget.left -= k.length;\n record[k] = pruneValue(v, budget, depth + 1, ancestors);\n }\n return record;\n } catch {\n return TRUNCATED;\n } finally {\n ancestors.delete(value);\n }\n}\n"],"mappings":";;;;;AAsGA,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,8BAA8B;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,aAAa,UAA6B,OAAsC;AACvF,QAAO,IAAI,IAAI,CAAC,GAAG,UAAU,GAAI,SAAS,EAAE,CAAE,CAAC,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC;;AAG7E,SAAS,WAAW,OAAuB;AACzC,KAAI;AACF,SAAO,mBAAmB,MAAM,QAAQ,OAAO,IAAI,CAAC;SAC9C;AACN,SAAO;;;;AAKX,SAAS,YAAY,KAAa,cAAmC;CACnE,MAAM,IAAI,IAAI,QAAQ,IAAI;AAC1B,KAAI,MAAM,GAAI,QAAO;CACrB,MAAM,SAAS,IAAI,QAAQ,KAAK,EAAE;CAClC,MAAM,QAAQ,IAAI,MAAM,IAAI,GAAG,WAAW,KAAK,SAAY,OAAO;CAClE,MAAM,OAAO,WAAW,KAAK,KAAK,IAAI,MAAM,OAAO;CACnD,MAAM,UAAU,MACb,MAAM,IAAI,CACV,KAAK,SAAS;EACb,MAAM,KAAK,KAAK,QAAQ,IAAI;EAC5B,MAAM,SAAS,OAAO,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG;EACnD,MAAM,UAAU,WAAW,OAAO;EAClC,MAAM,UAAU,QAAQ,QAAQ,IAAI;EACpC,MAAM,QAAQ,YAAY,KAAK,UAAU,QAAQ,MAAM,GAAG,QAAQ,EAAE,aAAa;AACjF,SAAO,aAAa,IAAI,KAAK,IAAI,OAAO,KAAK,GAAG,OAAO,eAAe;GACtE,CACD,KAAK,IAAI;AACZ,QAAO,GAAG,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG,UAAU;;AAGzC,SAAS,gBACP,SACA,WACyB;AACzB,KAAI,CAAC,QAAS,QAAO,EAAE;CACvB,MAAMA,MAA+B,EAAE;AACvC,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,QAAQ,EAAE;AAC5C,MAAI,MAAM,eAAe,MAAM,iBAAiB,MAAM,YAAa;AACnE,MAAI,KAAK,UAAU,IAAI,EAAE,aAAa,CAAC,GAAG,eAAe;;AAE3D,QAAO;;;AAIT,SAAS,KAAK,QAAuB;AACnC,KAAI,UAAU,OAAQ,OAA4B,UAAU,WAC1D,CAAC,OAA4B,OAAO,UAClCC,mCAAc,gCAAgC,MAAM,CACrD;;AAIL,SAAS,UAAkB;AAEzB,QAAO,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,GAAG;;AAQhD,SAAS,gBAAgB,SAAsC;AAC7D,QAAO;EACL,SAAS,aAAa,wBAAwB,QAAQ,cAAc;EACpE,aAAa,aAAa,6BAA6B,QAAQ,kBAAkB;EAClF;;AAGH,SAAS,gBACP,KACA,SACA,SACA,EAAE,SAAS,WAAW,aAAa,YACV;;CACzB,MAAMC,SAAkC;EACtC;EACA,wBAAQ,IAAI,kEAAQ,aAAa,KAAI;EACrC,KAAK,YAAY,IAAI,OAAO,KAAK,SAAS;EAC1C,IAAI,IAAI,sBAAM,IAAI,kEAAQ,kBAAiB;EAC3C,SAAS,gBAAgB,IAAI,SAAS,UAAU;EACjD;AAED,KAAI,QAAQ,YACV,QAAO,OAAO,QAAQ,QAAQ,YAAY,IAAI,CAAC;AAGjD,QAAO;;;;;AAQT,SAAgB,wBACd,QACA,UAA6B,EAAE,EAC0C;CACzE,MAAM,EACJ,MACA,SACA,cACA,gBAAgB,cAChB,eAAe,SACf,gBAAgB,QAChB,aAAa,SACb,yBAAyB,QACvB;CACJ,MAAM,WAAW,gBAAgB,QAAQ;CACzC,MAAM,kBAAkB,cAAc,aAAa;AAEnD,QAAO,SAAS,sBACd,KACA,KACA,MACM;;AACN,kDAAI,KAAO,IAAI,EAAE;AACf,SAAM;AACN;;EAGF,MAAM,UAAUC,uDAAmB,IAAI,qEAAU,iBAAiB,IAAI,SAAS;EAC/E,MAAM,UAAU,KAAK,KAAK;EAC1B,MAAM,aAAa,gBAAgB,KAAK,SAAS,SAAS,SAAS;AAGnE,MAAI,iBAAiB,SACnB,MACE,OAAO,SAAS,cAAc,mBAAmB;GAC/C,GAAG;GACH,GAAI,WAAW,IAAI,OAAO,EAAE,MAAM,aAAa,IAAI,MAAM,aAAa,EAAE,GAAG,EAAE;GAC9E,CAAC,CACH;EAUH,IAAI,YAAY;EAChB,MAAM,iBAAuB;AAC3B,OAAI,UAAW;AACf,eAAY;GAEZ,MAAM,WAAW,KAAK,KAAK,GAAG;GAC9B,MAAM,SAAS,IAAI,cAAc;GACjC,MAAM,QAAQ,UAAU,MAAM,aAAa;AAE3C,QACE,OAAO,SAAS,OAAO,qBAAqB;IAC1C,GAAG;IACH,YAAY;IACZ;IACD,CAAC,CACH;AAED,OAAI,WAAW,uBACb,MACE,OAAO,KAAK,yBAAyB;IACnC,GAAG;IACH,YAAY;IACZ;IACA,WAAW;IACZ,CAAC,CACH;;AAIL,mBAAI,8DAAO,UAAU,SAAS;AAG9B,oBAAI,gEAAO,SAAS,SAAS;AAE7B,QAAM;;;;;;;;;;;AAkBV,SAAgB,oBAAoB,QAAqB,UAA6B,EAAE,EAAE;CACxF,MAAM,EACJ,MACA,gBAAgB,cAChB,eAAe,SACf,gBAAgB,QAChB,aAAa,SACb,yBAAyB,QACvB;CACJ,MAAM,WAAW,gBAAgB,QAAQ;CACzC,MAAM,kBAAkB,cAAc,aAAa;CAEnD,SAAS,qBAAqB,SAA0B,OAAgB,MAAwB;AAC9F,UAAQ,QAAQ,cAAc,SAAkB,QAAiB,aAAyB;;GACxF,MAAM,MAAM;AACZ,mDAAI,KAAO,IAAI,EAAE;AACf,cAAU;AACV;;GAGF,MAAM,UAAUA,wDAAmB,IAAI,uEAAU,iBAAiB,IAAI,SAAS;AAC/E,OAAI,gBAAgB,KAAK,KAAK;AAC9B,OAAI,aAAa;AAEjB,OAAI,iBAAiB,SACnB,MACE,OAAO,SACL,cACA,mBACA,gBAAgB,KAAK,SAAS,SAAS,SAAS,CACjD,CACF;AAEH,aAAU;IACV;AAEF,UAAQ,QAAQ,eAAe,SAAkB,OAAgB,aAAyB;GACxF,MAAM,MAAM;AACZ,OAAI,IAAI,kBAAkB,QAAW;AACnC,cAAU;AACV;;GAGF,MAAM,MAAM;GACZ,MAAM,WAAW,KAAK,KAAK,GAAG,IAAI;GAClC,MAAM,SAAS,IAAI,cAAc;GACjC,MAAM,UAAU,IAAI,cAAc,SAAS;GAC3C,MAAM,QAAQ,UAAU,MAAM,aAAa;GAC3C,MAAM,aAAa,gBAAgB,KAAK,SAAS,SAAS,SAAS;AAEnE,QACE,OAAO,SAAS,OAAO,qBAAqB;IAC1C,GAAG;IACH,YAAY;IACZ;IACD,CAAC,CACH;AAED,OAAI,WAAW,uBACb,MACE,OAAO,KAAK,yBAAyB;IACnC;IACA,KAAK,WAAW;IAChB,QAAQ,IAAI;IACZ;IACA,WAAW;IACZ,CAAC,CACH;AAGH,aAAU;IACV;AAEF,QAAM;;AAGR,QAAO,OAAO,OAAO,sBAAsB;GACxC,OAAO,IAAI,gBAAgB,GAAG;GAC9B,OAAO,IAAI,uBAAuB,GAAG;EACvC,CAAC;;;;;;;AAUJ,SAAS,aAAa,MAAe,WAAW,MAAe;AAC7D,KAAI,OAAO,SAAS,SAClB,QAAO,KAAK,SAAS,WAAW,KAAK,MAAM,GAAG,SAAS,GAAG,iBAAiB;AAE7E,KAAI,QAAQ,OAAO,SAAS,UAAU;AAEpC,MADYC,uCAAgB,KAAK,CACzB,UAAU,SAAU,QAAO;AACnC,SAAO,WAAW,MAAM,EAAE,MAAM,UAAU,EAAE,mBAAG,IAAI,KAAK,CAAC;;AAE3D,QAAO;;AAGT,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAExB,SAAS,WACP,OACA,QACA,OACA,WACS;AACT,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC;AACpD,SAAO,QAAQ,IAAI;AACnB,SAAO,IAAI,SAAS,MAAM,SAAS,MAAM,YAAY;;AAEvD,KAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,SAAO,QAAQ;AACf,SAAO;;AAET,KAAI,iBAAiB,MAAM;AACzB,SAAO,QAAQ;AACf,SAAO,OAAO,MAAM,MAAM,SAAS,CAAC,GAAG,iBAAiB,MAAM,aAAa;;AAE7E,KAAI,iBAAiB,eAAe,YAAY,OAAO,MAAM,EAAE;AAC7D,SAAO,QAAQ;AACf,SAAO,WAAW,MAAM,WAAW;;AAErC,KAAI,iBAAiB,MAAO,QAAO;AACnC,KAAI,UAAU,IAAI,MAAM,IAAI,SAAS,gBAAiB,QAAO;AAC7D,WAAU,IAAI,MAAM;AACpB,KAAI;AACF,MAAI,MAAM,QAAQ,MAAM,IAAI,iBAAiB,OAAO,iBAAiB,KAAK;GACxE,MAAMC,QAAmB,EAAE;AAC3B,QAAK,MAAM,QAAQ,iBAAiB,MAAM,MAAM,SAAS,GAAG,OAAO;AACjE,QAAI,OAAO,QAAQ,GAAG;AACpB,WAAM,KAAK,UAAU;AACrB;;AAEF,UAAM,KAAK,WAAW,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC;;AAE5D,UAAO;;EAET,MAAM,SAAU,MAA+B;AAC/C,MAAI,OAAO,WAAW,YAAY;GAChC,MAAMC,OAAgB,OAAO,KAAK,MAAM;AACxC,OAAI,SAAS,MAAO,QAAO,WAAW,MAAM,QAAQ,QAAQ,GAAG,UAAU;;EAE3E,MAAMC,SAAkC,EAAE;AAC1C,OAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,EAAE;AAC1C,OAAI,MAAM,eAAe,MAAM,iBAAiB,MAAM,YAAa;AACnE,OAAI,OAAO,QAAQ,GAAG;AACpB,WAAO,OAAO;AACd;;AAEF,UAAO,QAAQ,EAAE;AACjB,UAAO,KAAK,WAAW,GAAG,QAAQ,QAAQ,GAAG,UAAU;;AAEzD,SAAO;SACD;AACN,SAAO;WACC;AACR,YAAU,OAAO,MAAM"}
1
+ {"version":3,"file":"middleware.js","names":["internalError","buildNameSet","DEFAULT_REDACT_HEADERS","DEFAULT_REDACT_QUERY_PARAMS","sanitizeUrl","resolveTraceHeaders","resolveRequestTraceId","stringifyForLog"],"sources":["../src/middleware/http-logger.ts"],"sourcesContent":["/**\n * HTTP request/response logging middleware — Morgan replacement.\n *\n * Fixes every documented Morgan bug:\n * - statusCode always captured correctly, even for requests > 20 s\n * - Logs request START (with traceId) and response FINISH (with duration)\n * - Captures errors before and after response\n * - Auto-redacts Authorization / Cookie / Set-Cookie headers\n * - Slow-request warnings\n * - Skip predicates for health-check routes / static assets\n *\n * @example Express\n * ```ts\n * import { createExpressMiddleware } from 'logixia/middleware';\n * app.use(createExpressMiddleware(logger));\n * ```\n *\n * @example Fastify\n * ```ts\n * import { createFastifyPlugin } from 'logixia/middleware';\n * await fastify.register(createFastifyPlugin(logger));\n * ```\n */\n\nimport type { IBaseLogger } from '../types';\nimport type { HttpRequestShape, HttpResponseShape } from '../types/http-shapes';\nimport { internalError } from '../utils/internal-log';\nimport { assignOwn } from '../utils/own-property';\nimport { resolveRequestTraceId, resolveTraceHeaders } from '../utils/request-trace';\nimport { stringifyForLog } from '../utils/safe-stringify';\nimport {\n buildNameSet,\n DEFAULT_REDACT_HEADERS,\n DEFAULT_REDACT_QUERY_PARAMS,\n redactUrlQuery as sanitizeUrl,\n} from '../utils/sensitive-names';\n\n// ── Shared types ─────────────────────────────────────────────────────────────\n\nexport interface HttpLoggerOptions {\n /**\n * Skip logging for a request. Called before any I/O.\n * @example `skip: (req) => req.url === '/health'`\n */\n skip?: (req: IncomingRequest) => boolean;\n /**\n * Log request body (POST/PUT/PATCH). Capped at `bodyMaxBytes` (default: 4096).\n * Redaction still applies to the captured body.\n * Default: false.\n */\n logBody?: boolean;\n /** Max bytes of body to capture. Default: 4096. */\n bodyMaxBytes?: number;\n /**\n * Emit a WARN log when a request duration exceeds this threshold (ms).\n * Default: 1000.\n */\n slowRequestThresholdMs?: number;\n /**\n * Additional fields to include in every log entry.\n * @example `extraFields: (req) => ({ tenantId: req.headers['x-tenant-id'] })`\n */\n extraFields?: (req: IncomingRequest) => Record<string, unknown>;\n /**\n * Header name, or list of names tried in order, to read the trace ID from.\n * Default: the library-wide list (`DEFAULT_TRACE_HEADERS`: traceparent,\n * x-trace-id, x-request-id, x-correlation-id, trace-id).\n * If none is present, `request.id` is used when it is not a framework counter,\n * otherwise a UUID is generated and shared with the logixia context helpers.\n */\n traceIdHeader?: string | readonly string[];\n /**\n * Additional headers to redact from logged output. Merged with the defaults\n * (authorization, cookie, set-cookie, x-api-key, proxy-authorization,\n * x-auth-token, x-access-token, x-csrf-token, x-amz-security-token).\n */\n redactHeaders?: string[];\n /**\n * Additional query-string parameter names whose values are redacted from the\n * logged URL. Merged with the defaults (token, access_token, refresh_token,\n * code, password, secret, api_key, apikey, key, signature, sig, auth).\n */\n redactQueryParams?: string[];\n /**\n * Log level for request-start entries. Default: 'debug'.\n * Set to 'silent' to suppress request-start logs entirely.\n */\n requestLevel?: string;\n /** Log level for successful response entries. Default: 'info'. */\n responseLevel?: string;\n /** Log level for error responses (status ≥ 500). Default: 'error'. */\n errorLevel?: string;\n}\n\n// Minimal structural types so we don't need @types/express / fastify in core\nexport type IncomingRequest = HttpRequestShape;\nexport type OutgoingResponse = HttpResponseShape;\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction sanitizeHeaders(\n headers: Record<string, string | string[] | undefined> | undefined,\n redactSet: Set<string>\n): Record<string, unknown> {\n if (!headers) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(headers)) {\n assignOwn(out, k, redactSet.has(k.toLowerCase()) ? '[REDACTED]' : v);\n }\n return out;\n}\n\n/** Fire-and-forget a logger call; a failing log must never become an unhandled rejection. */\nfunction fire(result: unknown): void {\n if (result && typeof (result as Promise<unknown>).catch === 'function') {\n (result as Promise<unknown>).catch((error: unknown) =>\n internalError('http-logger: log call failed', error)\n );\n }\n}\n\ninterface NameSets {\n headers: Set<string>;\n queryParams: Set<string>;\n}\n\nfunction resolveNameSets(options: HttpLoggerOptions): NameSets {\n return {\n headers: buildNameSet(DEFAULT_REDACT_HEADERS, options.redactHeaders),\n queryParams: buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams),\n };\n}\n\nfunction buildBaseFields(\n req: IncomingRequest,\n traceId: string,\n options: HttpLoggerOptions,\n { headers: redactSet, queryParams: paramSet }: NameSets\n): Record<string, unknown> {\n const fields: Record<string, unknown> = {\n traceId,\n method: req.method?.toUpperCase() ?? 'UNKNOWN',\n url: sanitizeUrl(req.url ?? '/', paramSet),\n ip: req.ip ?? req.socket?.remoteAddress ?? 'unknown',\n headers: sanitizeHeaders(req.headers, redactSet),\n };\n\n if (options.extraFields) {\n Object.assign(fields, options.extraFields(req));\n }\n\n return fields;\n}\n\n// ── Express middleware ────────────────────────────────────────────────────────\n\n/**\n * Create an Express / Connect compatible middleware that replaces Morgan.\n */\nexport function createExpressMiddleware(\n logger: IBaseLogger,\n options: HttpLoggerOptions = {}\n): (req: IncomingRequest, res: OutgoingResponse, next: () => void) => void {\n const {\n skip,\n logBody,\n bodyMaxBytes,\n traceIdHeader,\n requestLevel = 'debug',\n responseLevel = 'info',\n errorLevel = 'error',\n slowRequestThresholdMs = 1000,\n } = options;\n const nameSets = resolveNameSets(options);\n const traceHeaders = resolveTraceHeaders(traceIdHeader);\n\n return function logixiaHttpMiddleware(\n req: IncomingRequest,\n res: OutgoingResponse,\n next: () => void\n ): void {\n if (skip?.(req)) {\n next();\n return;\n }\n\n const traceId = resolveRequestTraceId(req, traceHeaders);\n const startMs = Date.now();\n const baseFields = buildBaseFields(req, traceId, options, nameSets);\n\n // Log request start\n if (requestLevel !== 'silent') {\n fire(\n logger.logLevel(requestLevel, 'request started', {\n ...baseFields,\n ...(logBody && req.body ? { body: truncateBody(req.body, bodyMaxBytes) } : {}),\n })\n );\n }\n\n // Hook into the response 'finish' event — fires after headers + body are sent.\n // This is what Morgan gets wrong for slow requests (it uses 'close' which may\n // fire before the status code is set on some Node versions).\n //\n // BOTH 'finish' and 'close' fire on a normal response, so guard against\n // logging the completion twice (which would duplicate the completed entry and\n // the slow-request warning).\n let completed = false;\n const onFinish = (): void => {\n if (completed) return;\n completed = true;\n\n const duration = Date.now() - startMs;\n const status = res.statusCode ?? 0;\n const level = status >= 500 ? errorLevel : responseLevel;\n\n fire(\n logger.logLevel(level, 'request completed', {\n ...baseFields,\n statusCode: status,\n duration,\n })\n );\n\n if (duration > slowRequestThresholdMs) {\n fire(\n logger.warn('slow request detected', {\n ...baseFields,\n statusCode: status,\n duration,\n threshold: slowRequestThresholdMs,\n })\n );\n }\n };\n\n res.once?.('finish', onFinish);\n // Fallback: also covers a client that disconnects before 'finish' fires. The\n // `completed` guard ensures a normal finish+close pair logs only once.\n res.once?.('close', onFinish);\n\n next();\n };\n}\n\n// ── Fastify plugin ─────────────────────────────────────────────────────────────\n\nexport interface FastifyInstance {\n addHook: (name: string, fn: (req: unknown, reply: unknown, done: () => void) => void) => void;\n}\n\n/**\n * Create a Fastify plugin (a function you pass to `fastify.register()`).\n *\n * @example\n * ```ts\n * await fastify.register(createFastifyPlugin(logger, { slowRequestThresholdMs: 500 }));\n * ```\n */\nexport function createFastifyPlugin(logger: IBaseLogger, options: HttpLoggerOptions = {}) {\n const {\n skip,\n traceIdHeader,\n requestLevel = 'debug',\n responseLevel = 'info',\n errorLevel = 'error',\n slowRequestThresholdMs = 1000,\n } = options;\n const nameSets = resolveNameSets(options);\n const traceHeaders = resolveTraceHeaders(traceIdHeader);\n\n function logixiaFastifyPlugin(fastify: FastifyInstance, _opts: unknown, done: () => void): void {\n fastify.addHook('onRequest', (request: unknown, _reply: unknown, hookDone: () => void) => {\n const req = request as IncomingRequest & { _logixiaStart?: number };\n if (skip?.(req)) {\n hookDone();\n return;\n }\n\n const traceId = resolveRequestTraceId(req, traceHeaders);\n req._logixiaStart = Date.now();\n\n if (requestLevel !== 'silent') {\n fire(\n logger.logLevel(\n requestLevel,\n 'request started',\n buildBaseFields(req, traceId, options, nameSets)\n )\n );\n }\n hookDone();\n });\n\n fastify.addHook('onResponse', (request: unknown, reply: unknown, hookDone: () => void) => {\n const req = request as IncomingRequest & { _logixiaStart?: number };\n if (req._logixiaStart === undefined) {\n hookDone();\n return;\n }\n\n const rep = reply as { statusCode?: number };\n const duration = Date.now() - req._logixiaStart;\n const status = rep.statusCode ?? 0;\n const traceId = resolveRequestTraceId(req, traceHeaders);\n const level = status >= 500 ? errorLevel : responseLevel;\n const baseFields = buildBaseFields(req, traceId, options, nameSets);\n\n fire(\n logger.logLevel(level, 'request completed', {\n ...baseFields,\n statusCode: status,\n duration,\n })\n );\n\n if (duration > slowRequestThresholdMs) {\n fire(\n logger.warn('slow request detected', {\n traceId,\n url: baseFields.url,\n method: req.method,\n duration,\n threshold: slowRequestThresholdMs,\n })\n );\n }\n\n hookDone();\n });\n\n done();\n }\n\n return Object.assign(logixiaFastifyPlugin, {\n [Symbol.for('skip-override')]: true,\n [Symbol.for('fastify.display-name')]: 'logixia',\n });\n}\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\n/**\n * Bound the captured body without turning it into a string, so the logger's\n * path-based redaction still sees the original keys. Oversized structures are\n * pruned (extra keys/items dropped, long strings cut) rather than serialized.\n */\nfunction truncateBody(body: unknown, maxBytes = 4096): unknown {\n if (typeof body === 'string') {\n return body.length > maxBytes ? body.slice(0, maxBytes) + '…[truncated]' : body;\n }\n if (body && typeof body === 'object') {\n const str = stringifyForLog(body);\n if (str.length <= maxBytes) return body;\n return pruneValue(body, { left: maxBytes }, 0, new Set());\n }\n return body;\n}\n\nconst TRUNCATED = '…[truncated]';\nconst MAX_PRUNE_DEPTH = 8;\n\nfunction pruneValue(\n value: unknown,\n budget: { left: number },\n depth: number,\n ancestors: Set<object>\n): unknown {\n if (typeof value === 'string') {\n const cut = value.slice(0, Math.max(0, budget.left));\n budget.left -= cut.length;\n return cut.length < value.length ? cut + TRUNCATED : cut;\n }\n if (value === null || typeof value !== 'object') {\n budget.left -= 8;\n return value;\n }\n if (value instanceof Date) {\n budget.left -= 24;\n return Number.isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString();\n }\n if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {\n budget.left -= 16;\n return `[Buffer ${value.byteLength} bytes]`;\n }\n if (value instanceof Error) return value;\n if (ancestors.has(value) || depth >= MAX_PRUNE_DEPTH) return TRUNCATED;\n ancestors.add(value);\n try {\n if (Array.isArray(value) || value instanceof Set || value instanceof Map) {\n const items: unknown[] = [];\n for (const item of value instanceof Map ? value.entries() : value) {\n if (budget.left <= 0) {\n items.push(TRUNCATED);\n break;\n }\n items.push(pruneValue(item, budget, depth + 1, ancestors));\n }\n return items;\n }\n const toJSON = (value as { toJSON?: unknown }).toJSON;\n if (typeof toJSON === 'function') {\n const json: unknown = toJSON.call(value);\n if (json !== value) return pruneValue(json, budget, depth + 1, ancestors);\n }\n const record: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n if (budget.left <= 0) {\n record['…'] = TRUNCATED;\n break;\n }\n budget.left -= k.length;\n assignOwn(record, k, pruneValue(v, budget, depth + 1, ancestors));\n }\n return record;\n } catch {\n return TRUNCATED;\n } finally {\n ancestors.delete(value);\n }\n}\n"],"mappings":"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"}
@@ -1,76 +1,18 @@
1
- import { t as internalError } from "./internal-log-Cm8SOWSV.mjs";
2
- import { i as stringifyForLog } from "./safe-stringify-DJ66tW5I.mjs";
3
- import { d as sanitizeIncomingId } from "./trace.utils-CvXgaiQT.mjs";
4
-
5
- //#region src/middleware/http-logger.ts
6
- const DEFAULT_REDACT_HEADERS = [
7
- "authorization",
8
- "cookie",
9
- "set-cookie",
10
- "x-api-key",
11
- "proxy-authorization",
12
- "x-auth-token",
13
- "x-access-token",
14
- "x-csrf-token",
15
- "x-amz-security-token"
16
- ];
17
- const DEFAULT_REDACT_QUERY_PARAMS = [
18
- "token",
19
- "access_token",
20
- "refresh_token",
21
- "code",
22
- "password",
23
- "secret",
24
- "api_key",
25
- "apikey",
26
- "key",
27
- "signature",
28
- "sig",
29
- "auth"
30
- ];
31
- function buildNameSet(defaults, extra) {
32
- return new Set([...defaults, ...extra ?? []].map((n) => n.toLowerCase()));
33
- }
34
- function safeDecode(value) {
35
- try {
36
- return decodeURIComponent(value.replace(/\+/g, " "));
37
- } catch {
38
- return value;
39
- }
40
- }
41
- /** Replace the values of sensitive query parameters, leaving the rest verbatim. */
42
- function sanitizeUrl(url, redactParams) {
43
- const q = url.indexOf("?");
44
- if (q === -1) return url;
45
- const hashAt = url.indexOf("#", q);
46
- const query = url.slice(q + 1, hashAt === -1 ? void 0 : hashAt);
47
- const hash = hashAt === -1 ? "" : url.slice(hashAt);
48
- const cleaned = query.split("&").map((pair) => {
49
- const eq = pair.indexOf("=");
50
- const rawKey = eq === -1 ? pair : pair.slice(0, eq);
51
- const decoded = safeDecode(rawKey);
52
- const bracket = decoded.indexOf("[");
53
- const name = (bracket === -1 ? decoded : decoded.slice(0, bracket)).toLowerCase();
54
- return redactParams.has(name) && eq !== -1 ? `${rawKey}=[REDACTED]` : pair;
55
- }).join("&");
56
- return `${url.slice(0, q)}?${cleaned}${hash}`;
57
- }
1
+ import { t as assignOwn } from "./own-property-CjFw9qwZ.mjs";
2
+ import { n as resolveTraceHeaders, t as resolveRequestTraceId } from "./request-trace-BgNykH9M.mjs";
3
+ import { a as DEFAULT_REDACT_QUERY_PARAMS, c as buildNameSet, i as DEFAULT_REDACT_HEADERS, l as redactUrlQuery, t as internalError } from "./internal-log-Bg4V1cPg.mjs";
4
+ import { i as stringifyForLog } from "./safe-stringify-B5p5ktFe.mjs";
5
+ // src/middleware/http-logger.ts
58
6
  function sanitizeHeaders(headers, redactSet) {
59
7
  if (!headers) return {};
60
8
  const out = {};
61
- for (const [k, v] of Object.entries(headers)) {
62
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
63
- out[k] = redactSet.has(k.toLowerCase()) ? "[REDACTED]" : v;
64
- }
9
+ for (const [k, v] of Object.entries(headers)) assignOwn(out, k, redactSet.has(k.toLowerCase()) ? "[REDACTED]" : v);
65
10
  return out;
66
11
  }
67
12
  /** Fire-and-forget a logger call; a failing log must never become an unhandled rejection. */
68
13
  function fire(result) {
69
14
  if (result && typeof result.catch === "function") result.catch((error) => internalError("http-logger: log call failed", error));
70
15
  }
71
- function shortId() {
72
- return Math.random().toString(36).slice(2, 10);
73
- }
74
16
  function resolveNameSets(options) {
75
17
  return {
76
18
  headers: buildNameSet(DEFAULT_REDACT_HEADERS, options.redactHeaders),
@@ -82,7 +24,7 @@ function buildBaseFields(req, traceId, options, { headers: redactSet, queryParam
82
24
  const fields = {
83
25
  traceId,
84
26
  method: ((_req$method = req.method) === null || _req$method === void 0 ? void 0 : _req$method.toUpperCase()) ?? "UNKNOWN",
85
- url: sanitizeUrl(req.url ?? "/", paramSet),
27
+ url: redactUrlQuery(req.url ?? "/", paramSet),
86
28
  ip: req.ip ?? ((_req$socket = req.socket) === null || _req$socket === void 0 ? void 0 : _req$socket.remoteAddress) ?? "unknown",
87
29
  headers: sanitizeHeaders(req.headers, redactSet)
88
30
  };
@@ -93,16 +35,16 @@ function buildBaseFields(req, traceId, options, { headers: redactSet, queryParam
93
35
  * Create an Express / Connect compatible middleware that replaces Morgan.
94
36
  */
95
37
  function createExpressMiddleware(logger, options = {}) {
96
- const { skip, logBody, bodyMaxBytes, traceIdHeader = "x-trace-id", requestLevel = "debug", responseLevel = "info", errorLevel = "error", slowRequestThresholdMs = 1e3 } = options;
38
+ const { skip, logBody, bodyMaxBytes, traceIdHeader, requestLevel = "debug", responseLevel = "info", errorLevel = "error", slowRequestThresholdMs = 1e3 } = options;
97
39
  const nameSets = resolveNameSets(options);
98
- const traceHeaderName = traceIdHeader.toLowerCase();
40
+ const traceHeaders = resolveTraceHeaders(traceIdHeader);
99
41
  return function logixiaHttpMiddleware(req, res, next) {
100
- var _req$headers, _res$once, _res$once2;
42
+ var _res$once, _res$once2;
101
43
  if (skip === null || skip === void 0 ? void 0 : skip(req)) {
102
44
  next();
103
45
  return;
104
46
  }
105
- const traceId = sanitizeIncomingId((_req$headers = req.headers) === null || _req$headers === void 0 ? void 0 : _req$headers[traceHeaderName]) ?? shortId();
47
+ const traceId = resolveRequestTraceId(req, traceHeaders);
106
48
  const startMs = Date.now();
107
49
  const baseFields = buildBaseFields(req, traceId, options, nameSets);
108
50
  if (requestLevel !== "silent") fire(logger.logLevel(requestLevel, "request started", {
@@ -142,20 +84,18 @@ function createExpressMiddleware(logger, options = {}) {
142
84
  * ```
143
85
  */
144
86
  function createFastifyPlugin(logger, options = {}) {
145
- const { skip, traceIdHeader = "x-trace-id", requestLevel = "debug", responseLevel = "info", errorLevel = "error", slowRequestThresholdMs = 1e3 } = options;
87
+ const { skip, traceIdHeader, requestLevel = "debug", responseLevel = "info", errorLevel = "error", slowRequestThresholdMs = 1e3 } = options;
146
88
  const nameSets = resolveNameSets(options);
147
- const traceHeaderName = traceIdHeader.toLowerCase();
89
+ const traceHeaders = resolveTraceHeaders(traceIdHeader);
148
90
  function logixiaFastifyPlugin(fastify, _opts, done) {
149
91
  fastify.addHook("onRequest", (request, _reply, hookDone) => {
150
- var _req$headers2;
151
92
  const req = request;
152
93
  if (skip === null || skip === void 0 ? void 0 : skip(req)) {
153
94
  hookDone();
154
95
  return;
155
96
  }
156
- const traceId = sanitizeIncomingId((_req$headers2 = req.headers) === null || _req$headers2 === void 0 ? void 0 : _req$headers2[traceHeaderName]) ?? shortId();
97
+ const traceId = resolveRequestTraceId(req, traceHeaders);
157
98
  req._logixiaStart = Date.now();
158
- req._logixiaId = traceId;
159
99
  if (requestLevel !== "silent") fire(logger.logLevel(requestLevel, "request started", buildBaseFields(req, traceId, options, nameSets)));
160
100
  hookDone();
161
101
  });
@@ -168,7 +108,7 @@ function createFastifyPlugin(logger, options = {}) {
168
108
  const rep = reply;
169
109
  const duration = Date.now() - req._logixiaStart;
170
110
  const status = rep.statusCode ?? 0;
171
- const traceId = req._logixiaId ?? shortId();
111
+ const traceId = resolveRequestTraceId(req, traceHeaders);
172
112
  const level = status >= 500 ? errorLevel : responseLevel;
173
113
  const baseFields = buildBaseFields(req, traceId, options, nameSets);
174
114
  fire(logger.logLevel(level, "request completed", {
@@ -247,13 +187,12 @@ function pruneValue(value, budget, depth, ancestors) {
247
187
  }
248
188
  const record = {};
249
189
  for (const [k, v] of Object.entries(value)) {
250
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
251
190
  if (budget.left <= 0) {
252
191
  record["…"] = TRUNCATED;
253
192
  break;
254
193
  }
255
194
  budget.left -= k.length;
256
- record[k] = pruneValue(v, budget, depth + 1, ancestors);
195
+ assignOwn(record, k, pruneValue(v, budget, depth + 1, ancestors));
257
196
  }
258
197
  return record;
259
198
  } catch {
@@ -262,7 +201,6 @@ function pruneValue(value, budget, depth, ancestors) {
262
201
  ancestors.delete(value);
263
202
  }
264
203
  }
265
-
266
- //#endregion
267
204
  export { createExpressMiddleware, createFastifyPlugin };
205
+
268
206
  //# sourceMappingURL=middleware.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.mjs","names":["out: Record<string, unknown>","fields: Record<string, unknown>","items: unknown[]","json: unknown","record: Record<string, unknown>"],"sources":["../src/middleware/http-logger.ts"],"sourcesContent":["/**\n * HTTP request/response logging middleware — Morgan replacement.\n *\n * Fixes every documented Morgan bug:\n * - statusCode always captured correctly, even for requests > 20 s\n * - Logs request START (with traceId) and response FINISH (with duration)\n * - Captures errors before and after response\n * - Auto-redacts Authorization / Cookie / Set-Cookie headers\n * - Slow-request warnings\n * - Skip predicates for health-check routes / static assets\n *\n * @example Express\n * ```ts\n * import { createExpressMiddleware } from 'logixia/middleware';\n * app.use(createExpressMiddleware(logger));\n * ```\n *\n * @example Fastify\n * ```ts\n * import { createFastifyPlugin } from 'logixia/middleware';\n * await fastify.register(createFastifyPlugin(logger));\n * ```\n */\n\nimport type { IBaseLogger } from '../types';\nimport { internalError } from '../utils/internal-log';\nimport { stringifyForLog } from '../utils/safe-stringify';\nimport { sanitizeIncomingId } from '../utils/trace.utils';\n\n// ── Shared types ─────────────────────────────────────────────────────────────\n\nexport interface HttpLoggerOptions {\n /**\n * Skip logging for a request. Called before any I/O.\n * @example `skip: (req) => req.url === '/health'`\n */\n skip?: (req: IncomingRequest) => boolean;\n /**\n * Log request body (POST/PUT/PATCH). Capped at `bodyMaxBytes` (default: 4096).\n * Redaction still applies to the captured body.\n * Default: false.\n */\n logBody?: boolean;\n /** Max bytes of body to capture. Default: 4096. */\n bodyMaxBytes?: number;\n /**\n * Emit a WARN log when a request duration exceeds this threshold (ms).\n * Default: 1000.\n */\n slowRequestThresholdMs?: number;\n /**\n * Additional fields to include in every log entry.\n * @example `extraFields: (req) => ({ tenantId: req.headers['x-tenant-id'] })`\n */\n extraFields?: (req: IncomingRequest) => Record<string, unknown>;\n /**\n * Trace ID header. Default: 'x-trace-id'.\n * If the header is absent, a short random ID is generated.\n */\n traceIdHeader?: string;\n /**\n * Additional headers to redact from logged output. Merged with the defaults\n * (authorization, cookie, set-cookie, x-api-key, proxy-authorization,\n * x-auth-token, x-access-token, x-csrf-token, x-amz-security-token).\n */\n redactHeaders?: string[];\n /**\n * Additional query-string parameter names whose values are redacted from the\n * logged URL. Merged with the defaults (token, access_token, refresh_token,\n * code, password, secret, api_key, apikey, key, signature, sig, auth).\n */\n redactQueryParams?: string[];\n /**\n * Log level for request-start entries. Default: 'debug'.\n * Set to 'silent' to suppress request-start logs entirely.\n */\n requestLevel?: string;\n /** Log level for successful response entries. Default: 'info'. */\n responseLevel?: string;\n /** Log level for error responses (status ≥ 500). Default: 'error'. */\n errorLevel?: string;\n}\n\n// Minimal structural types so we don't need @types/express / fastify in core\nexport interface IncomingRequest {\n method?: string;\n url?: string;\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n socket?: { remoteAddress?: string };\n ip?: string;\n}\n\nexport interface OutgoingResponse {\n statusCode?: number;\n on?: (event: string, cb: () => void) => void;\n once?: (event: string, cb: () => void) => void;\n getHeader?: (name: string) => string | number | string[] | undefined;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nconst DEFAULT_REDACT_HEADERS = [\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'proxy-authorization',\n 'x-auth-token',\n 'x-access-token',\n 'x-csrf-token',\n 'x-amz-security-token',\n];\n\nconst DEFAULT_REDACT_QUERY_PARAMS = [\n 'token',\n 'access_token',\n 'refresh_token',\n 'code',\n 'password',\n 'secret',\n 'api_key',\n 'apikey',\n 'key',\n 'signature',\n 'sig',\n 'auth',\n];\n\nfunction buildNameSet(defaults: readonly string[], extra: readonly string[] | undefined) {\n return new Set([...defaults, ...(extra ?? [])].map((n) => n.toLowerCase()));\n}\n\nfunction safeDecode(value: string): string {\n try {\n return decodeURIComponent(value.replace(/\\+/g, ' '));\n } catch {\n return value;\n }\n}\n\n/** Replace the values of sensitive query parameters, leaving the rest verbatim. */\nfunction sanitizeUrl(url: string, redactParams: Set<string>): string {\n const q = url.indexOf('?');\n if (q === -1) return url;\n const hashAt = url.indexOf('#', q);\n const query = url.slice(q + 1, hashAt === -1 ? undefined : hashAt);\n const hash = hashAt === -1 ? '' : url.slice(hashAt);\n const cleaned = query\n .split('&')\n .map((pair) => {\n const eq = pair.indexOf('=');\n const rawKey = eq === -1 ? pair : pair.slice(0, eq);\n const decoded = safeDecode(rawKey);\n const bracket = decoded.indexOf('[');\n const name = (bracket === -1 ? decoded : decoded.slice(0, bracket)).toLowerCase();\n return redactParams.has(name) && eq !== -1 ? `${rawKey}=[REDACTED]` : pair;\n })\n .join('&');\n return `${url.slice(0, q)}?${cleaned}${hash}`;\n}\n\nfunction sanitizeHeaders(\n headers: Record<string, string | string[] | undefined> | undefined,\n redactSet: Set<string>\n): Record<string, unknown> {\n if (!headers) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(headers)) {\n if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;\n out[k] = redactSet.has(k.toLowerCase()) ? '[REDACTED]' : v;\n }\n return out;\n}\n\n/** Fire-and-forget a logger call; a failing log must never become an unhandled rejection. */\nfunction fire(result: unknown): void {\n if (result && typeof (result as Promise<unknown>).catch === 'function') {\n (result as Promise<unknown>).catch((error: unknown) =>\n internalError('http-logger: log call failed', error)\n );\n }\n}\n\nfunction shortId(): string {\n // eslint-disable-next-line sonarjs/pseudo-random -- non-security request ID\n return Math.random().toString(36).slice(2, 10);\n}\n\ninterface NameSets {\n headers: Set<string>;\n queryParams: Set<string>;\n}\n\nfunction resolveNameSets(options: HttpLoggerOptions): NameSets {\n return {\n headers: buildNameSet(DEFAULT_REDACT_HEADERS, options.redactHeaders),\n queryParams: buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams),\n };\n}\n\nfunction buildBaseFields(\n req: IncomingRequest,\n traceId: string,\n options: HttpLoggerOptions,\n { headers: redactSet, queryParams: paramSet }: NameSets\n): Record<string, unknown> {\n const fields: Record<string, unknown> = {\n traceId,\n method: req.method?.toUpperCase() ?? 'UNKNOWN',\n url: sanitizeUrl(req.url ?? '/', paramSet),\n ip: req.ip ?? req.socket?.remoteAddress ?? 'unknown',\n headers: sanitizeHeaders(req.headers, redactSet),\n };\n\n if (options.extraFields) {\n Object.assign(fields, options.extraFields(req));\n }\n\n return fields;\n}\n\n// ── Express middleware ────────────────────────────────────────────────────────\n\n/**\n * Create an Express / Connect compatible middleware that replaces Morgan.\n */\nexport function createExpressMiddleware(\n logger: IBaseLogger,\n options: HttpLoggerOptions = {}\n): (req: IncomingRequest, res: OutgoingResponse, next: () => void) => void {\n const {\n skip,\n logBody,\n bodyMaxBytes,\n traceIdHeader = 'x-trace-id',\n requestLevel = 'debug',\n responseLevel = 'info',\n errorLevel = 'error',\n slowRequestThresholdMs = 1000,\n } = options;\n const nameSets = resolveNameSets(options);\n const traceHeaderName = traceIdHeader.toLowerCase();\n\n return function logixiaHttpMiddleware(\n req: IncomingRequest,\n res: OutgoingResponse,\n next: () => void\n ): void {\n if (skip?.(req)) {\n next();\n return;\n }\n\n const traceId = sanitizeIncomingId(req.headers?.[traceHeaderName]) ?? shortId();\n const startMs = Date.now();\n const baseFields = buildBaseFields(req, traceId, options, nameSets);\n\n // Log request start\n if (requestLevel !== 'silent') {\n fire(\n logger.logLevel(requestLevel, 'request started', {\n ...baseFields,\n ...(logBody && req.body ? { body: truncateBody(req.body, bodyMaxBytes) } : {}),\n })\n );\n }\n\n // Hook into the response 'finish' event — fires after headers + body are sent.\n // This is what Morgan gets wrong for slow requests (it uses 'close' which may\n // fire before the status code is set on some Node versions).\n //\n // BOTH 'finish' and 'close' fire on a normal response, so guard against\n // logging the completion twice (which would duplicate the completed entry and\n // the slow-request warning).\n let completed = false;\n const onFinish = (): void => {\n if (completed) return;\n completed = true;\n\n const duration = Date.now() - startMs;\n const status = res.statusCode ?? 0;\n const level = status >= 500 ? errorLevel : responseLevel;\n\n fire(\n logger.logLevel(level, 'request completed', {\n ...baseFields,\n statusCode: status,\n duration,\n })\n );\n\n if (duration > slowRequestThresholdMs) {\n fire(\n logger.warn('slow request detected', {\n ...baseFields,\n statusCode: status,\n duration,\n threshold: slowRequestThresholdMs,\n })\n );\n }\n };\n\n res.once?.('finish', onFinish);\n // Fallback: also covers a client that disconnects before 'finish' fires. The\n // `completed` guard ensures a normal finish+close pair logs only once.\n res.once?.('close', onFinish);\n\n next();\n };\n}\n\n// ── Fastify plugin ─────────────────────────────────────────────────────────────\n\nexport interface FastifyInstance {\n addHook: (name: string, fn: (req: unknown, reply: unknown, done: () => void) => void) => void;\n}\n\n/**\n * Create a Fastify plugin (a function you pass to `fastify.register()`).\n *\n * @example\n * ```ts\n * await fastify.register(createFastifyPlugin(logger, { slowRequestThresholdMs: 500 }));\n * ```\n */\nexport function createFastifyPlugin(logger: IBaseLogger, options: HttpLoggerOptions = {}) {\n const {\n skip,\n traceIdHeader = 'x-trace-id',\n requestLevel = 'debug',\n responseLevel = 'info',\n errorLevel = 'error',\n slowRequestThresholdMs = 1000,\n } = options;\n const nameSets = resolveNameSets(options);\n const traceHeaderName = traceIdHeader.toLowerCase();\n\n function logixiaFastifyPlugin(fastify: FastifyInstance, _opts: unknown, done: () => void): void {\n fastify.addHook('onRequest', (request: unknown, _reply: unknown, hookDone: () => void) => {\n const req = request as IncomingRequest & { _logixiaStart?: number; _logixiaId?: string };\n if (skip?.(req)) {\n hookDone();\n return;\n }\n\n const traceId = sanitizeIncomingId(req.headers?.[traceHeaderName]) ?? shortId();\n req._logixiaStart = Date.now();\n req._logixiaId = traceId;\n\n if (requestLevel !== 'silent') {\n fire(\n logger.logLevel(\n requestLevel,\n 'request started',\n buildBaseFields(req, traceId, options, nameSets)\n )\n );\n }\n hookDone();\n });\n\n fastify.addHook('onResponse', (request: unknown, reply: unknown, hookDone: () => void) => {\n const req = request as IncomingRequest & { _logixiaStart?: number; _logixiaId?: string };\n if (req._logixiaStart === undefined) {\n hookDone();\n return;\n }\n\n const rep = reply as { statusCode?: number };\n const duration = Date.now() - req._logixiaStart;\n const status = rep.statusCode ?? 0;\n const traceId = req._logixiaId ?? shortId();\n const level = status >= 500 ? errorLevel : responseLevel;\n const baseFields = buildBaseFields(req, traceId, options, nameSets);\n\n fire(\n logger.logLevel(level, 'request completed', {\n ...baseFields,\n statusCode: status,\n duration,\n })\n );\n\n if (duration > slowRequestThresholdMs) {\n fire(\n logger.warn('slow request detected', {\n traceId,\n url: baseFields.url,\n method: req.method,\n duration,\n threshold: slowRequestThresholdMs,\n })\n );\n }\n\n hookDone();\n });\n\n done();\n }\n\n return Object.assign(logixiaFastifyPlugin, {\n [Symbol.for('skip-override')]: true,\n [Symbol.for('fastify.display-name')]: 'logixia',\n });\n}\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\n/**\n * Bound the captured body without turning it into a string, so the logger's\n * path-based redaction still sees the original keys. Oversized structures are\n * pruned (extra keys/items dropped, long strings cut) rather than serialized.\n */\nfunction truncateBody(body: unknown, maxBytes = 4096): unknown {\n if (typeof body === 'string') {\n return body.length > maxBytes ? body.slice(0, maxBytes) + '…[truncated]' : body;\n }\n if (body && typeof body === 'object') {\n const str = stringifyForLog(body);\n if (str.length <= maxBytes) return body;\n return pruneValue(body, { left: maxBytes }, 0, new Set());\n }\n return body;\n}\n\nconst TRUNCATED = '…[truncated]';\nconst MAX_PRUNE_DEPTH = 8;\n\nfunction pruneValue(\n value: unknown,\n budget: { left: number },\n depth: number,\n ancestors: Set<object>\n): unknown {\n if (typeof value === 'string') {\n const cut = value.slice(0, Math.max(0, budget.left));\n budget.left -= cut.length;\n return cut.length < value.length ? cut + TRUNCATED : cut;\n }\n if (value === null || typeof value !== 'object') {\n budget.left -= 8;\n return value;\n }\n if (value instanceof Date) {\n budget.left -= 24;\n return Number.isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString();\n }\n if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {\n budget.left -= 16;\n return `[Buffer ${value.byteLength} bytes]`;\n }\n if (value instanceof Error) return value;\n if (ancestors.has(value) || depth >= MAX_PRUNE_DEPTH) return TRUNCATED;\n ancestors.add(value);\n try {\n if (Array.isArray(value) || value instanceof Set || value instanceof Map) {\n const items: unknown[] = [];\n for (const item of value instanceof Map ? value.entries() : value) {\n if (budget.left <= 0) {\n items.push(TRUNCATED);\n break;\n }\n items.push(pruneValue(item, budget, depth + 1, ancestors));\n }\n return items;\n }\n const toJSON = (value as { toJSON?: unknown }).toJSON;\n if (typeof toJSON === 'function') {\n const json: unknown = toJSON.call(value);\n if (json !== value) return pruneValue(json, budget, depth + 1, ancestors);\n }\n const record: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;\n if (budget.left <= 0) {\n record['…'] = TRUNCATED;\n break;\n }\n budget.left -= k.length;\n record[k] = pruneValue(v, budget, depth + 1, ancestors);\n }\n return record;\n } catch {\n return TRUNCATED;\n } finally {\n ancestors.delete(value);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"middleware.mjs","names":["sanitizeUrl"],"sources":["../src/middleware/http-logger.ts"],"sourcesContent":["/**\n * HTTP request/response logging middleware — Morgan replacement.\n *\n * Fixes every documented Morgan bug:\n * - statusCode always captured correctly, even for requests > 20 s\n * - Logs request START (with traceId) and response FINISH (with duration)\n * - Captures errors before and after response\n * - Auto-redacts Authorization / Cookie / Set-Cookie headers\n * - Slow-request warnings\n * - Skip predicates for health-check routes / static assets\n *\n * @example Express\n * ```ts\n * import { createExpressMiddleware } from 'logixia/middleware';\n * app.use(createExpressMiddleware(logger));\n * ```\n *\n * @example Fastify\n * ```ts\n * import { createFastifyPlugin } from 'logixia/middleware';\n * await fastify.register(createFastifyPlugin(logger));\n * ```\n */\n\nimport type { IBaseLogger } from '../types';\nimport type { HttpRequestShape, HttpResponseShape } from '../types/http-shapes';\nimport { internalError } from '../utils/internal-log';\nimport { assignOwn } from '../utils/own-property';\nimport { resolveRequestTraceId, resolveTraceHeaders } from '../utils/request-trace';\nimport { stringifyForLog } from '../utils/safe-stringify';\nimport {\n buildNameSet,\n DEFAULT_REDACT_HEADERS,\n DEFAULT_REDACT_QUERY_PARAMS,\n redactUrlQuery as sanitizeUrl,\n} from '../utils/sensitive-names';\n\n// ── Shared types ─────────────────────────────────────────────────────────────\n\nexport interface HttpLoggerOptions {\n /**\n * Skip logging for a request. Called before any I/O.\n * @example `skip: (req) => req.url === '/health'`\n */\n skip?: (req: IncomingRequest) => boolean;\n /**\n * Log request body (POST/PUT/PATCH). Capped at `bodyMaxBytes` (default: 4096).\n * Redaction still applies to the captured body.\n * Default: false.\n */\n logBody?: boolean;\n /** Max bytes of body to capture. Default: 4096. */\n bodyMaxBytes?: number;\n /**\n * Emit a WARN log when a request duration exceeds this threshold (ms).\n * Default: 1000.\n */\n slowRequestThresholdMs?: number;\n /**\n * Additional fields to include in every log entry.\n * @example `extraFields: (req) => ({ tenantId: req.headers['x-tenant-id'] })`\n */\n extraFields?: (req: IncomingRequest) => Record<string, unknown>;\n /**\n * Header name, or list of names tried in order, to read the trace ID from.\n * Default: the library-wide list (`DEFAULT_TRACE_HEADERS`: traceparent,\n * x-trace-id, x-request-id, x-correlation-id, trace-id).\n * If none is present, `request.id` is used when it is not a framework counter,\n * otherwise a UUID is generated and shared with the logixia context helpers.\n */\n traceIdHeader?: string | readonly string[];\n /**\n * Additional headers to redact from logged output. Merged with the defaults\n * (authorization, cookie, set-cookie, x-api-key, proxy-authorization,\n * x-auth-token, x-access-token, x-csrf-token, x-amz-security-token).\n */\n redactHeaders?: string[];\n /**\n * Additional query-string parameter names whose values are redacted from the\n * logged URL. Merged with the defaults (token, access_token, refresh_token,\n * code, password, secret, api_key, apikey, key, signature, sig, auth).\n */\n redactQueryParams?: string[];\n /**\n * Log level for request-start entries. Default: 'debug'.\n * Set to 'silent' to suppress request-start logs entirely.\n */\n requestLevel?: string;\n /** Log level for successful response entries. Default: 'info'. */\n responseLevel?: string;\n /** Log level for error responses (status ≥ 500). Default: 'error'. */\n errorLevel?: string;\n}\n\n// Minimal structural types so we don't need @types/express / fastify in core\nexport type IncomingRequest = HttpRequestShape;\nexport type OutgoingResponse = HttpResponseShape;\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction sanitizeHeaders(\n headers: Record<string, string | string[] | undefined> | undefined,\n redactSet: Set<string>\n): Record<string, unknown> {\n if (!headers) return {};\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(headers)) {\n assignOwn(out, k, redactSet.has(k.toLowerCase()) ? '[REDACTED]' : v);\n }\n return out;\n}\n\n/** Fire-and-forget a logger call; a failing log must never become an unhandled rejection. */\nfunction fire(result: unknown): void {\n if (result && typeof (result as Promise<unknown>).catch === 'function') {\n (result as Promise<unknown>).catch((error: unknown) =>\n internalError('http-logger: log call failed', error)\n );\n }\n}\n\ninterface NameSets {\n headers: Set<string>;\n queryParams: Set<string>;\n}\n\nfunction resolveNameSets(options: HttpLoggerOptions): NameSets {\n return {\n headers: buildNameSet(DEFAULT_REDACT_HEADERS, options.redactHeaders),\n queryParams: buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams),\n };\n}\n\nfunction buildBaseFields(\n req: IncomingRequest,\n traceId: string,\n options: HttpLoggerOptions,\n { headers: redactSet, queryParams: paramSet }: NameSets\n): Record<string, unknown> {\n const fields: Record<string, unknown> = {\n traceId,\n method: req.method?.toUpperCase() ?? 'UNKNOWN',\n url: sanitizeUrl(req.url ?? '/', paramSet),\n ip: req.ip ?? req.socket?.remoteAddress ?? 'unknown',\n headers: sanitizeHeaders(req.headers, redactSet),\n };\n\n if (options.extraFields) {\n Object.assign(fields, options.extraFields(req));\n }\n\n return fields;\n}\n\n// ── Express middleware ────────────────────────────────────────────────────────\n\n/**\n * Create an Express / Connect compatible middleware that replaces Morgan.\n */\nexport function createExpressMiddleware(\n logger: IBaseLogger,\n options: HttpLoggerOptions = {}\n): (req: IncomingRequest, res: OutgoingResponse, next: () => void) => void {\n const {\n skip,\n logBody,\n bodyMaxBytes,\n traceIdHeader,\n requestLevel = 'debug',\n responseLevel = 'info',\n errorLevel = 'error',\n slowRequestThresholdMs = 1000,\n } = options;\n const nameSets = resolveNameSets(options);\n const traceHeaders = resolveTraceHeaders(traceIdHeader);\n\n return function logixiaHttpMiddleware(\n req: IncomingRequest,\n res: OutgoingResponse,\n next: () => void\n ): void {\n if (skip?.(req)) {\n next();\n return;\n }\n\n const traceId = resolveRequestTraceId(req, traceHeaders);\n const startMs = Date.now();\n const baseFields = buildBaseFields(req, traceId, options, nameSets);\n\n // Log request start\n if (requestLevel !== 'silent') {\n fire(\n logger.logLevel(requestLevel, 'request started', {\n ...baseFields,\n ...(logBody && req.body ? { body: truncateBody(req.body, bodyMaxBytes) } : {}),\n })\n );\n }\n\n // Hook into the response 'finish' event — fires after headers + body are sent.\n // This is what Morgan gets wrong for slow requests (it uses 'close' which may\n // fire before the status code is set on some Node versions).\n //\n // BOTH 'finish' and 'close' fire on a normal response, so guard against\n // logging the completion twice (which would duplicate the completed entry and\n // the slow-request warning).\n let completed = false;\n const onFinish = (): void => {\n if (completed) return;\n completed = true;\n\n const duration = Date.now() - startMs;\n const status = res.statusCode ?? 0;\n const level = status >= 500 ? errorLevel : responseLevel;\n\n fire(\n logger.logLevel(level, 'request completed', {\n ...baseFields,\n statusCode: status,\n duration,\n })\n );\n\n if (duration > slowRequestThresholdMs) {\n fire(\n logger.warn('slow request detected', {\n ...baseFields,\n statusCode: status,\n duration,\n threshold: slowRequestThresholdMs,\n })\n );\n }\n };\n\n res.once?.('finish', onFinish);\n // Fallback: also covers a client that disconnects before 'finish' fires. The\n // `completed` guard ensures a normal finish+close pair logs only once.\n res.once?.('close', onFinish);\n\n next();\n };\n}\n\n// ── Fastify plugin ─────────────────────────────────────────────────────────────\n\nexport interface FastifyInstance {\n addHook: (name: string, fn: (req: unknown, reply: unknown, done: () => void) => void) => void;\n}\n\n/**\n * Create a Fastify plugin (a function you pass to `fastify.register()`).\n *\n * @example\n * ```ts\n * await fastify.register(createFastifyPlugin(logger, { slowRequestThresholdMs: 500 }));\n * ```\n */\nexport function createFastifyPlugin(logger: IBaseLogger, options: HttpLoggerOptions = {}) {\n const {\n skip,\n traceIdHeader,\n requestLevel = 'debug',\n responseLevel = 'info',\n errorLevel = 'error',\n slowRequestThresholdMs = 1000,\n } = options;\n const nameSets = resolveNameSets(options);\n const traceHeaders = resolveTraceHeaders(traceIdHeader);\n\n function logixiaFastifyPlugin(fastify: FastifyInstance, _opts: unknown, done: () => void): void {\n fastify.addHook('onRequest', (request: unknown, _reply: unknown, hookDone: () => void) => {\n const req = request as IncomingRequest & { _logixiaStart?: number };\n if (skip?.(req)) {\n hookDone();\n return;\n }\n\n const traceId = resolveRequestTraceId(req, traceHeaders);\n req._logixiaStart = Date.now();\n\n if (requestLevel !== 'silent') {\n fire(\n logger.logLevel(\n requestLevel,\n 'request started',\n buildBaseFields(req, traceId, options, nameSets)\n )\n );\n }\n hookDone();\n });\n\n fastify.addHook('onResponse', (request: unknown, reply: unknown, hookDone: () => void) => {\n const req = request as IncomingRequest & { _logixiaStart?: number };\n if (req._logixiaStart === undefined) {\n hookDone();\n return;\n }\n\n const rep = reply as { statusCode?: number };\n const duration = Date.now() - req._logixiaStart;\n const status = rep.statusCode ?? 0;\n const traceId = resolveRequestTraceId(req, traceHeaders);\n const level = status >= 500 ? errorLevel : responseLevel;\n const baseFields = buildBaseFields(req, traceId, options, nameSets);\n\n fire(\n logger.logLevel(level, 'request completed', {\n ...baseFields,\n statusCode: status,\n duration,\n })\n );\n\n if (duration > slowRequestThresholdMs) {\n fire(\n logger.warn('slow request detected', {\n traceId,\n url: baseFields.url,\n method: req.method,\n duration,\n threshold: slowRequestThresholdMs,\n })\n );\n }\n\n hookDone();\n });\n\n done();\n }\n\n return Object.assign(logixiaFastifyPlugin, {\n [Symbol.for('skip-override')]: true,\n [Symbol.for('fastify.display-name')]: 'logixia',\n });\n}\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\n/**\n * Bound the captured body without turning it into a string, so the logger's\n * path-based redaction still sees the original keys. Oversized structures are\n * pruned (extra keys/items dropped, long strings cut) rather than serialized.\n */\nfunction truncateBody(body: unknown, maxBytes = 4096): unknown {\n if (typeof body === 'string') {\n return body.length > maxBytes ? body.slice(0, maxBytes) + '…[truncated]' : body;\n }\n if (body && typeof body === 'object') {\n const str = stringifyForLog(body);\n if (str.length <= maxBytes) return body;\n return pruneValue(body, { left: maxBytes }, 0, new Set());\n }\n return body;\n}\n\nconst TRUNCATED = '…[truncated]';\nconst MAX_PRUNE_DEPTH = 8;\n\nfunction pruneValue(\n value: unknown,\n budget: { left: number },\n depth: number,\n ancestors: Set<object>\n): unknown {\n if (typeof value === 'string') {\n const cut = value.slice(0, Math.max(0, budget.left));\n budget.left -= cut.length;\n return cut.length < value.length ? cut + TRUNCATED : cut;\n }\n if (value === null || typeof value !== 'object') {\n budget.left -= 8;\n return value;\n }\n if (value instanceof Date) {\n budget.left -= 24;\n return Number.isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString();\n }\n if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {\n budget.left -= 16;\n return `[Buffer ${value.byteLength} bytes]`;\n }\n if (value instanceof Error) return value;\n if (ancestors.has(value) || depth >= MAX_PRUNE_DEPTH) return TRUNCATED;\n ancestors.add(value);\n try {\n if (Array.isArray(value) || value instanceof Set || value instanceof Map) {\n const items: unknown[] = [];\n for (const item of value instanceof Map ? value.entries() : value) {\n if (budget.left <= 0) {\n items.push(TRUNCATED);\n break;\n }\n items.push(pruneValue(item, budget, depth + 1, ancestors));\n }\n return items;\n }\n const toJSON = (value as { toJSON?: unknown }).toJSON;\n if (typeof toJSON === 'function') {\n const json: unknown = toJSON.call(value);\n if (json !== value) return pruneValue(json, budget, depth + 1, ancestors);\n }\n const record: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n if (budget.left <= 0) {\n record['…'] = TRUNCATED;\n break;\n }\n budget.left -= k.length;\n assignOwn(record, k, pruneValue(v, budget, depth + 1, ancestors));\n }\n return record;\n } catch {\n return TRUNCATED;\n } finally {\n ancestors.delete(value);\n }\n}\n"],"mappings":"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"}
package/dist/nest.d.mts CHANGED
@@ -1,13 +1,8 @@
1
- import "./transport.types-BMnurPcc.mjs";
2
- import { E as TraceIdConfig, v as LogLevelString, y as LoggerConfig } from "./index-3mw5gaCw.mjs";
3
- import { t as LogixiaLogger } from "./logitron-logger-BFDSO8Io.mjs";
4
- import * as _nestjs_common0 from "@nestjs/common";
1
+ import { A as TraceIdConfig, b as LoggerConfig, y as LogLevelString } from "./index-BSTtFcQ6.mjs";
2
+ import { t as LogixiaLogger } from "./logitron-logger-D22Yb61K.mjs";
5
3
  import { ArgumentsHost, CallHandler, ExceptionFilter, ExecutionContext, InjectionToken, LoggerService, MiddlewareConsumer, ModuleMetadata, NestInterceptor, NestMiddleware, NestModule, OptionalFactoryDependency, Type } from "@nestjs/common";
6
4
  import { Observable } from "rxjs";
7
5
  import { NextFunction, Request, Response } from "express";
8
-
9
- //#region src/core/logitron-nestjs.service.d.ts
10
-
11
6
  /**
12
7
  * Level names that already have proper typed implementations on LogixiaLoggerService.
13
8
  * These are excluded from the auto-generated method type so we don't produce
@@ -22,7 +17,7 @@ type _ServiceBuiltinLevels = 'error' | 'warn' | 'info' | 'debug' | 'verbose' | '
22
17
  * ServiceCustomLevelMethods<{ kafka: 3; mysql: 4; payment: 5 }>
23
18
  * // → { kafka(msg, data?): Promise<void>; mysql(...): …; payment(...): … }
24
19
  */
25
- type ServiceCustomLevelMethods<TLevels extends Record<string, number>> = { readonly [K in keyof TLevels as K extends _ServiceBuiltinLevels ? never : K & string]: (message: string, data?: Record<string, unknown>) => Promise<void> };
20
+ type ServiceCustomLevelMethods<TLevels extends Record<string, number>> = { readonly [K in keyof TLevels as K extends _ServiceBuiltinLevels ? never : K & string]: (message: string, data?: Record<string, unknown>) => Promise<void>; };
26
21
  /**
27
22
  * The return type of `LogixiaLoggerService.create<T>(config)`.
28
23
  *
@@ -30,7 +25,7 @@ type ServiceCustomLevelMethods<TLevels extends Record<string, number>> = { reado
30
25
  * `LogixiaLoggerService` with a method for every *custom* level so the IDE
31
26
  * suggests `service.kafka(...)`, `service.payment(...)`, etc.
32
27
  */
33
- type LogixiaServiceWithLevels<T extends LoggerConfig<Record<string, number>>> = T['levelOptions'] extends {
28
+ export type LogixiaServiceWithLevels<T extends LoggerConfig<Record<string, number>>> = T['levelOptions'] extends {
34
29
  levels: infer L;
35
30
  } ? L extends Record<string, number> ? LogixiaLoggerService & ServiceCustomLevelMethods<L> : LogixiaLoggerService : LogixiaLoggerService;
36
31
  /**
@@ -72,8 +67,8 @@ type _ExtractCustomLevelNames<T> = T extends {
72
67
  * type AppLogger = LogixiaServiceWith<'kafka' | 'payment'>;
73
68
  * ```
74
69
  */
75
- type LogixiaServiceWith<T extends string | Record<string, unknown>> = LogixiaLoggerService & { readonly [K in T extends string ? Exclude<T, _ServiceBuiltinLevels> : _ExtractCustomLevelNames<T>]: (message: string, data?: Record<string, unknown>) => Promise<void> };
76
- declare class LogixiaLoggerService implements LoggerService {
70
+ export type LogixiaServiceWith<T extends string | Record<string, unknown>> = LogixiaLoggerService & { readonly [K in T extends string ? Exclude<T, _ServiceBuiltinLevels> : _ExtractCustomLevelNames<T>]: (message: string, data?: Record<string, unknown>) => Promise<void>; };
71
+ export declare class LogixiaLoggerService implements LoggerService {
77
72
  [K: string]: any;
78
73
  private logger;
79
74
  private context?;
@@ -168,8 +163,6 @@ declare class LogixiaLoggerService implements LoggerService {
168
163
  private setContextIfProvided;
169
164
  private formatMessage;
170
165
  }
171
- //#endregion
172
- //#region src/core/kafka-trace.interceptor.d.ts
173
166
  /**
174
167
  * Observable counters exposed for monitoring Kafka consumer trace hygiene.
175
168
  *
@@ -187,8 +180,7 @@ interface KafkaTraceMetrics {
187
180
  /** Messages dropped because `requireTraceId: true` and no traceId was found. */
188
181
  dropped: number;
189
182
  }
190
- declare class KafkaTraceInterceptor implements NestInterceptor {
191
- private readonly config?;
183
+ export declare class KafkaTraceInterceptor implements NestInterceptor {
192
184
  private readonly requireTraceId;
193
185
  /**
194
186
  * Process-wide counters. Readable from anywhere — scrape into your metrics
@@ -198,6 +190,7 @@ declare class KafkaTraceInterceptor implements NestInterceptor {
198
190
  /** Reset counters (tests). */
199
191
  static resetMetrics(): void;
200
192
  private readonly ctx;
193
+ private readonly config;
201
194
  /**
202
195
  * @param config - TraceIdConfig options (extractor keys, contextKey, etc.)
203
196
  * @param requireTraceId - When true, messages with no traceId are silently skipped
@@ -206,35 +199,31 @@ declare class KafkaTraceInterceptor implements NestInterceptor {
206
199
  * increments so the missing traceId is observable end-to-end.
207
200
  * Default: false (handler runs without trace context).
208
201
  */
209
- constructor(config?: TraceIdConfig | undefined, requireTraceId?: boolean);
202
+ constructor(config?: TraceIdConfig, requireTraceId?: boolean);
210
203
  intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
211
204
  }
212
- //#endregion
213
- //#region src/core/websocket-trace.interceptor.d.ts
214
- declare class WebSocketTraceInterceptor implements NestInterceptor {
215
- private readonly config?;
205
+ export declare class WebSocketTraceInterceptor implements NestInterceptor {
216
206
  private readonly ctx;
217
- constructor(config?: TraceIdConfig | undefined);
207
+ private readonly config;
208
+ constructor(config?: TraceIdConfig);
218
209
  intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
219
210
  }
220
- //#endregion
221
- //#region src/core/logitron-logger.module.d.ts
222
- declare const LOGIXIA_LOGGER_CONFIG = "LOGIXIA_LOGGER_CONFIG";
223
- declare const LOGIXIA_TRACE_CONFIG = "TRACE_CONFIG";
224
- declare const LOGIXIA_LOGGER_PREFIX = "LOGIXIA_LOGGER_";
225
- interface LogixiaAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
211
+ export declare const LOGIXIA_LOGGER_CONFIG = "LOGIXIA_LOGGER_CONFIG";
212
+ export declare const LOGIXIA_TRACE_CONFIG = "TRACE_CONFIG";
213
+ export declare const LOGIXIA_LOGGER_PREFIX = "LOGIXIA_LOGGER_";
214
+ export interface LogixiaAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
226
215
  useExisting?: Type<LogixiaOptionsFactory>;
227
216
  useClass?: Type<LogixiaOptionsFactory>;
228
217
  useFactory?: (...args: unknown[]) => Promise<Partial<LoggerConfig>> | Partial<LoggerConfig>;
229
218
  inject?: Array<InjectionToken | OptionalFactoryDependency>;
230
219
  }
231
- interface LogixiaOptionsFactory {
220
+ export interface LogixiaOptionsFactory {
232
221
  createLogixiaOptions(): Promise<Partial<LoggerConfig>> | Partial<LoggerConfig>;
233
222
  }
234
223
  /**
235
224
  * Logixia Logger Module for NestJS dependency injection
236
225
  */
237
- declare class LogixiaLoggerModule implements NestModule {
226
+ export declare class LogixiaLoggerModule implements NestModule {
238
227
  private config;
239
228
  private static loggerConfig;
240
229
  /**
@@ -317,7 +306,7 @@ declare class LogixiaLoggerModule implements NestModule {
317
306
  */
318
307
  static forRootAsync(options: LogixiaAsyncOptions): {
319
308
  module: typeof LogixiaLoggerModule;
320
- imports: (Type<any> | _nestjs_common0.DynamicModule | Promise<_nestjs_common0.DynamicModule> | _nestjs_common0.ForwardReference<any>)[];
309
+ imports: (Type<any> | import("@nestjs/common").DynamicModule | Promise<import("@nestjs/common").DynamicModule> | import("@nestjs/common").ForwardReference<any>)[];
321
310
  providers: ({
322
311
  provide: string;
323
312
  useFactory: (...args: unknown[]) => Promise<Partial<LoggerConfig>> | Partial<LoggerConfig>;
@@ -364,20 +353,23 @@ declare class LogixiaLoggerModule implements NestModule {
364
353
  private static createAsyncProviders;
365
354
  private static createAsyncOptionsProvider;
366
355
  }
367
- //#endregion
368
- //#region src/core/nestjs-extras.d.ts
369
356
  /**
370
357
  * Inject the Logixia logger registered in the current NestJS DI container.
371
358
  *
372
- * Equivalent to `@Inject(LOGIXIA_LOGGER_TOKEN)` but without needing to import
373
- * the internal token constant yourself.
359
+ * With no argument it injects the root `LogixiaLoggerService` provided by
360
+ * `LogixiaLoggerModule.forRoot[Async]()`. With a `context` it injects the child
361
+ * logger that `LogixiaLoggerModule.forFeature(context)` provides for that context
362
+ * (the feature module must be imported where it is used).
374
363
  *
375
364
  * @example
376
365
  * ```ts
377
366
  * constructor(@InjectLogger() private readonly logger: LogixiaLoggerService) {}
367
+ *
368
+ * // with LogixiaLoggerModule.forFeature('orders') imported:
369
+ * constructor(@InjectLogger('orders') private readonly logger: LogixiaLoggerService) {}
378
370
  * ```
379
371
  */
380
- declare const InjectLogger: () => ParameterDecorator;
372
+ export declare const InjectLogger: (context?: string) => ParameterDecorator;
381
373
  interface LogMethodOptions {
382
374
  /**
383
375
  * Log level to use for entry / exit messages.
@@ -420,7 +412,7 @@ interface LogMethodOptions {
420
412
  * async processPayment(orderId: string): Promise<void> { … }
421
413
  * ```
422
414
  */
423
- declare function LogMethod(options?: LogMethodOptions): MethodDecorator;
415
+ export declare function LogMethod(options?: LogMethodOptions): MethodDecorator;
424
416
  /**
425
417
  * Global NestJS exception filter that converts any exception into the standard
426
418
  * `LogixiaErrorResponse` wire format and logs it with full request context.
@@ -438,8 +430,10 @@ declare function LogMethod(options?: LogMethodOptions): MethodDecorator;
438
430
  * `new LogixiaExceptionFilter(logger, undefined, { exposeDebug: process.env.NODE_ENV === 'development' })`.
439
431
  *
440
432
  * **Trace ID headers:**
441
- * `X-Trace-ID` and `X-Request-ID` are echoed back on every error response so
442
- * clients can correlate with server logs.
433
+ * The request's trace ID is written to one response header — `X-Trace-ID` by
434
+ * default, or the name set via `traceId.responseHeader` (`false` disables it) — so
435
+ * clients can correlate with server logs. The filter does not write
436
+ * `X-Request-ID`.
443
437
  *
444
438
  * **Retry-After header:**
445
439
  * Automatically added for `429` responses (`Retry-After: 60`).
@@ -463,16 +457,14 @@ interface LogixiaExceptionFilterOptions {
463
457
  exposeCause?: boolean;
464
458
  }
465
459
  /** Injection token for {@link LogixiaExceptionFilterOptions}. */
466
- declare const LOGIXIA_EXCEPTION_FILTER_OPTIONS = "LOGIXIA_EXCEPTION_FILTER_OPTIONS";
467
- declare class LogixiaExceptionFilter implements ExceptionFilter {
460
+ export declare const LOGIXIA_EXCEPTION_FILTER_OPTIONS = "LOGIXIA_EXCEPTION_FILTER_OPTIONS";
461
+ export declare class LogixiaExceptionFilter implements ExceptionFilter {
468
462
  private readonly _logger;
469
463
  private readonly _traceConfig;
470
464
  private readonly _options;
471
465
  constructor(logger?: LogixiaLoggerService, loggerConfig?: Partial<LoggerConfig>, options?: LogixiaExceptionFilterOptions);
472
466
  catch(exception: unknown, host: ArgumentsHost): void;
473
467
  }
474
- //#endregion
475
- //#region src/core/trace.middleware.d.ts
476
468
  declare global {
477
469
  namespace Express {
478
470
  interface Request {
@@ -480,20 +472,17 @@ declare global {
480
472
  }
481
473
  }
482
474
  }
483
- declare class TraceMiddleware implements NestMiddleware {
475
+ export declare class TraceMiddleware implements NestMiddleware {
484
476
  private readonly config?;
485
477
  private readonly ctx;
486
478
  constructor(config?: TraceIdConfig | undefined);
487
479
  use(req: Request, res: Response, next: NextFunction): void;
488
480
  }
489
- //#endregion
490
- //#region src/nest.d.ts
491
481
  /**
492
482
  * Create a new Logixia logger service for NestJS
493
483
  * @param config - Logger configuration
494
484
  * @returns LogixiaLoggerService instance
495
485
  */
496
- declare function createLoggerService(config?: Partial<LoggerConfig>): LogixiaLoggerService;
497
- //#endregion
498
- export { InjectLogger, KafkaTraceInterceptor, LOGIXIA_EXCEPTION_FILTER_OPTIONS, LOGIXIA_LOGGER_CONFIG, LOGIXIA_LOGGER_PREFIX, LOGIXIA_TRACE_CONFIG, LogMethod, type LogMethodOptions, LogixiaAsyncOptions, LogixiaExceptionFilter, type LogixiaExceptionFilterOptions, LogixiaLoggerModule, LogixiaLoggerService, LogixiaOptionsFactory, LogixiaServiceWith, LogixiaServiceWithLevels, TraceMiddleware, WebSocketTraceInterceptor, createLoggerService };
486
+ export declare function createLoggerService(config?: Partial<LoggerConfig>): LogixiaLoggerService;
487
+ export type { LogMethodOptions, LogixiaExceptionFilterOptions };
499
488
  //# sourceMappingURL=nest.d.mts.map