logixia 1.13.0 → 1.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/{async-context-CwBNMm2E.mjs → async-context-BzJrbfQs.mjs} +8 -4
- package/dist/async-context-BzJrbfQs.mjs.map +1 -0
- package/dist/{async-context-CSHWoHMB.js → async-context-DyQQYq3I.js} +8 -4
- package/dist/async-context-DyQQYq3I.js.map +1 -0
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts → azure-monitor.transport-BDFO4F-i.d.ts} +2 -2
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-BDFO4F-i.d.ts.map} +1 -1
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D5wQEH04.d.mts} +2 -2
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts.map → azure-monitor.transport-D5wQEH04.d.mts.map} +1 -1
- package/dist/azure.d.mts +2 -2
- package/dist/azure.d.ts +2 -2
- package/dist/cli/index.js +98 -52
- package/dist/cloudwatch.d.mts +2 -2
- package/dist/cloudwatch.d.ts +2 -2
- package/dist/cloudwatch.js +2 -1
- package/dist/cloudwatch.mjs +2 -1
- package/dist/{cloudwatch.transport-CGHY4buE.d.mts → cloudwatch.transport-B00st6Xv.d.ts} +9 -2
- package/dist/cloudwatch.transport-B00st6Xv.d.ts.map +1 -0
- package/dist/{cloudwatch.transport-pNBNWJLt.js → cloudwatch.transport-BO1ahxw-.js} +26 -19
- package/dist/cloudwatch.transport-BO1ahxw-.js.map +1 -0
- package/dist/{cloudwatch.transport-DK_8cKd3.d.ts → cloudwatch.transport-Blc7Gtxt.d.mts} +9 -2
- package/dist/cloudwatch.transport-Blc7Gtxt.d.mts.map +1 -0
- package/dist/{cloudwatch.transport-yBwm4OeO.mjs → cloudwatch.transport-DZ6ies7g.mjs} +26 -19
- package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +1 -0
- package/dist/{coerce.utils-BN980Y9V.js → coerce.utils-B-LilukN.js} +1 -1
- package/dist/{coerce.utils-BN980Y9V.js.map → coerce.utils-B-LilukN.js.map} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs → coerce.utils-Cu6IUTgX.mjs} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs.map → coerce.utils-Cu6IUTgX.mjs.map} +1 -1
- package/dist/correlation.d.mts +24 -6
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +24 -6
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +41 -16
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +41 -16
- package/dist/correlation.mjs.map +1 -1
- package/dist/entry-fields-BJkuPnIg.mjs +30 -0
- package/dist/entry-fields-BJkuPnIg.mjs.map +1 -0
- package/dist/entry-fields-BvOLFlJ4.js +36 -0
- package/dist/entry-fields-BvOLFlJ4.js.map +1 -0
- package/dist/gcp.d.mts +2 -2
- package/dist/gcp.d.ts +2 -2
- package/dist/gcp.js +2 -1
- package/dist/gcp.mjs +2 -1
- package/dist/{gcp.transport-SHdtc1n0.js → gcp.transport-CHV8MGgz.js} +5 -5
- package/dist/gcp.transport-CHV8MGgz.js.map +1 -0
- package/dist/{gcp.transport-BJuNRFe_.mjs → gcp.transport-DBX5TPIy.mjs} +5 -5
- package/dist/gcp.transport-DBX5TPIy.mjs.map +1 -0
- package/dist/{gcp.transport-BOhkVKlI.d.ts → gcp.transport-m4fVw0b1.d.ts} +2 -2
- package/dist/gcp.transport-m4fVw0b1.d.ts.map +1 -0
- package/dist/{gcp.transport-Bi6CUqZR.d.mts → gcp.transport-zzNyIoih.d.mts} +2 -2
- package/dist/gcp.transport-zzNyIoih.d.mts.map +1 -0
- package/dist/{index-BPKwwERo.d.mts → index-BPwEisy4.d.mts} +2 -2
- package/dist/{index-BPKwwERo.d.mts.map → index-BPwEisy4.d.mts.map} +1 -1
- package/dist/{index-C1X-TE5I.d.ts → index-nZEeCr3X.d.ts} +2 -2
- package/dist/{index-C1X-TE5I.d.ts.map → index-nZEeCr3X.d.ts.map} +1 -1
- package/dist/index.d.mts +63 -98
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +63 -98
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -443
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -377
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +11 -4
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +11 -4
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +148 -24
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +148 -24
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +4 -4
- package/dist/nest.d.ts +4 -4
- package/dist/nest.js +20 -11
- package/dist/nest.mjs +7 -5
- package/dist/{logitron-logger.module-BsDZR5BH.js → nestjs-extras-DnJ6vrIC.js} +562 -444
- package/dist/nestjs-extras-DnJ6vrIC.js.map +1 -0
- package/dist/{logitron-logger.module-Bs3XYZgF.mjs → nestjs-extras-ImbuDhs1.mjs} +505 -349
- package/dist/nestjs-extras-ImbuDhs1.mjs.map +1 -0
- package/dist/{logitron-logger.module-CbXy2gfW.d.ts → nestjs-extras-OkC796py.d.mts} +111 -4
- package/dist/nestjs-extras-OkC796py.d.mts.map +1 -0
- package/dist/{logitron-logger.module-BdG1Jnpk.d.mts → nestjs-extras-mKrHE9S6.d.ts} +111 -4
- package/dist/nestjs-extras-mKrHE9S6.d.ts.map +1 -0
- package/dist/{search-njTI2hdo.js → search-B0MH1SJJ.js} +23 -10
- package/dist/search-B0MH1SJJ.js.map +1 -0
- package/dist/{search-DujhR94O.d.ts → search-BfBzDIT9.d.mts} +2 -3
- package/dist/search-BfBzDIT9.d.mts.map +1 -0
- package/dist/{search-CQOo-Iq6.d.mts → search-CHUZPl0h.d.ts} +2 -3
- package/dist/search-CHUZPl0h.d.ts.map +1 -0
- package/dist/{search-CFQRISUv.mjs → search-DjXSn6v5.mjs} +23 -10
- package/dist/search-DjXSn6v5.mjs.map +1 -0
- package/dist/search.d.mts +3 -3
- package/dist/search.d.ts +3 -3
- package/dist/search.js +2 -2
- package/dist/search.mjs +2 -2
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/trace.utils-CvXgaiQT.mjs +278 -0
- package/dist/trace.utils-CvXgaiQT.mjs.map +1 -0
- package/dist/trace.utils-DpBOLDtq.js +358 -0
- package/dist/trace.utils-DpBOLDtq.js.map +1 -0
- package/dist/{transport.manager-D6KL3t9V.mjs → transport.manager-CpdVHEGP.mjs} +365 -86
- package/dist/transport.manager-CpdVHEGP.mjs.map +1 -0
- package/dist/{transport.manager-BivyUTtU.js → transport.manager-Crau3Rle.js} +370 -85
- package/dist/transport.manager-Crau3Rle.js.map +1 -0
- package/dist/{transport.types-B5vcyv2k.d.mts → transport.types-CtNr3D7S.d.mts} +19 -2
- package/dist/transport.types-CtNr3D7S.d.mts.map +1 -0
- package/dist/{transport.types-BuY84TwO.d.ts → transport.types-DqVLZTyf.d.ts} +19 -2
- package/dist/transport.types-DqVLZTyf.d.ts.map +1 -0
- package/dist/transports.d.mts +38 -13
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +38 -13
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +6 -5
- package/dist/transports.mjs +6 -5
- package/dist/worker-entry.js +307 -55
- package/dist/worker.d.mts +2 -2
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +1 -1
- package/dist/worker.mjs +1 -1
- package/dist/{worker.transport-BP8Q28Z2.d.mts → worker.transport-Bgv9heDI.d.mts} +2 -2
- package/dist/{worker.transport-BP8Q28Z2.d.mts.map → worker.transport-Bgv9heDI.d.mts.map} +1 -1
- package/dist/{worker.transport-mKXgNK0L.d.ts → worker.transport-Bj-i0P9V.d.ts} +2 -2
- package/dist/{worker.transport-mKXgNK0L.d.ts.map → worker.transport-Bj-i0P9V.d.ts.map} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js → worker.transport-JP8YHBuU.js} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js.map → worker.transport-JP8YHBuU.js.map} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs → worker.transport-otwhJ_Et.mjs} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs.map → worker.transport-otwhJ_Et.mjs.map} +1 -1
- package/package.json +10 -6
- package/dist/.tsbuildinfo +0 -1
- package/dist/async-context-CSHWoHMB.js.map +0 -1
- package/dist/async-context-CwBNMm2E.mjs.map +0 -1
- package/dist/cloudwatch.transport-CGHY4buE.d.mts.map +0 -1
- package/dist/cloudwatch.transport-DK_8cKd3.d.ts.map +0 -1
- package/dist/cloudwatch.transport-pNBNWJLt.js.map +0 -1
- package/dist/cloudwatch.transport-yBwm4OeO.mjs.map +0 -1
- package/dist/gcp.transport-BJuNRFe_.mjs.map +0 -1
- package/dist/gcp.transport-BOhkVKlI.d.ts.map +0 -1
- package/dist/gcp.transport-Bi6CUqZR.d.mts.map +0 -1
- package/dist/gcp.transport-SHdtc1n0.js.map +0 -1
- package/dist/logitron-logger.module-BdG1Jnpk.d.mts.map +0 -1
- package/dist/logitron-logger.module-Bs3XYZgF.mjs.map +0 -1
- package/dist/logitron-logger.module-BsDZR5BH.js.map +0 -1
- package/dist/logitron-logger.module-CbXy2gfW.d.ts.map +0 -1
- package/dist/search-CFQRISUv.mjs.map +0 -1
- package/dist/search-CQOo-Iq6.d.mts.map +0 -1
- package/dist/search-DujhR94O.d.ts.map +0 -1
- package/dist/search-njTI2hdo.js.map +0 -1
- package/dist/transport.manager-BivyUTtU.js.map +0 -1
- package/dist/transport.manager-D6KL3t9V.mjs.map +0 -1
- package/dist/transport.types-B5vcyv2k.d.mts.map +0 -1
- package/dist/transport.types-BuY84TwO.d.ts.map +0 -1
- package/scripts/postinstall.js +0 -61
package/README.md
CHANGED
|
@@ -1291,6 +1291,15 @@ The filter returns a consistent structured shape on every error:
|
|
|
1291
1291
|
}
|
|
1292
1292
|
```
|
|
1293
1293
|
|
|
1294
|
+
The `debug` block (stack trace, service, duration) is **off by default** and is never sent to clients unless you opt in, for example in local development only:
|
|
1295
|
+
|
|
1296
|
+
```typescript
|
|
1297
|
+
new LogixiaExceptionFilter(undefined, undefined, {
|
|
1298
|
+
exposeDebug: process.env.NODE_ENV === 'development',
|
|
1299
|
+
// exposeCause: true, // also include the underlying error message (may contain internal detail)
|
|
1300
|
+
});
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1294
1303
|
And every log line carries `method`, `url`, `status`, `request_id` as structured fields — not a plain string:
|
|
1295
1304
|
|
|
1296
1305
|
```
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { d as sanitizeIncomingId } from "./trace.utils-CvXgaiQT.mjs";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
4
|
|
|
@@ -21,7 +22,10 @@ const LogixiaContext = {
|
|
|
21
22
|
set(fields) {
|
|
22
23
|
const store = _storage.getStore();
|
|
23
24
|
if (!store) return;
|
|
24
|
-
Object.
|
|
25
|
+
for (const key of Object.keys(fields)) {
|
|
26
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
27
|
+
store[key] = fields[key];
|
|
28
|
+
}
|
|
25
29
|
},
|
|
26
30
|
getStorage() {
|
|
27
31
|
return _storage;
|
|
@@ -40,7 +44,7 @@ const LogixiaContext = {
|
|
|
40
44
|
function createExpressContextMiddleware(options = {}) {
|
|
41
45
|
const { enrich, traceIdHeader = "x-trace-id" } = options;
|
|
42
46
|
return function logixiaContextMiddleware(req, _res, next) {
|
|
43
|
-
const base = { traceId: (req["headers"] ?? {})[traceIdHeader] ?? randomShortId() };
|
|
47
|
+
const base = { traceId: sanitizeIncomingId((req["headers"] ?? {})[traceIdHeader.toLowerCase()]) ?? randomShortId() };
|
|
44
48
|
LogixiaContext.run({
|
|
45
49
|
...base,
|
|
46
50
|
...enrich ? enrich(req) : {}
|
|
@@ -55,7 +59,7 @@ function createExpressContextMiddleware(options = {}) {
|
|
|
55
59
|
function createFastifyContextHook(options = {}) {
|
|
56
60
|
const { enrich, traceIdHeader = "x-trace-id" } = options;
|
|
57
61
|
return function logixiaFastifyHook(request, _reply, done) {
|
|
58
|
-
const base = { traceId: (request["headers"] ?? {})[traceIdHeader] ?? request["id"] ?? randomShortId() };
|
|
62
|
+
const base = { traceId: sanitizeIncomingId((request["headers"] ?? {})[traceIdHeader.toLowerCase()]) ?? sanitizeIncomingId(request["id"]) ?? randomShortId() };
|
|
59
63
|
LogixiaContext.run({
|
|
60
64
|
...base,
|
|
61
65
|
...enrich ? enrich(request) : {}
|
|
@@ -65,4 +69,4 @@ function createFastifyContextHook(options = {}) {
|
|
|
65
69
|
|
|
66
70
|
//#endregion
|
|
67
71
|
export { createExpressContextMiddleware as n, createFastifyContextHook as r, LogixiaContext as t };
|
|
68
|
-
//# sourceMappingURL=async-context-
|
|
72
|
+
//# sourceMappingURL=async-context-BzJrbfQs.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-context-BzJrbfQs.mjs","names":["base: LogContext"],"sources":["../src/context/async-context.ts"],"sourcesContent":["/**\n * LogixiaContext — Zero-boilerplate AsyncLocalStorage context propagation.\n *\n * Lets every `logger.info()` call inside a context automatically pick up stored\n * fields (requestId, userId, tenantId, …) without passing them through every function.\n *\n * Works across `Promise.all`, `setTimeout`, and event emitters — anything that\n * inherits the async execution context.\n *\n * @example\n * ```ts\n * import { LogixiaContext } from 'logixia';\n *\n * // Wrap a request handler:\n * app.use((req, res, next) => {\n * LogixiaContext.run({ requestId: req.id, userId: req.user?.id }, next);\n * });\n *\n * // Anywhere deeper in the call tree:\n * async function processOrder(orderId: string) {\n * const ctx = LogixiaContext.get(); // { requestId, userId, orderId }\n * logger.info('Processing order', { orderId, ...ctx });\n * }\n * ```\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { randomUUID } from 'node:crypto';\n\nimport { sanitizeIncomingId } from '../utils/trace.utils';\n\n/** Generates an 8-character random ID using Node's crypto module. */\nfunction randomShortId(): string {\n return randomUUID().slice(0, 8);\n}\n\nexport interface LogContext {\n traceId?: string;\n spanId?: string;\n userId?: string;\n tenantId?: string;\n sessionId?: string;\n [key: string]: unknown;\n}\n\n// Module-level singleton so all loggers share the same ALS store.\nconst _storage = new AsyncLocalStorage<LogContext>();\n\nexport const LogixiaContext = {\n /**\n * Run `callback` inside an async context that carries `store` fields.\n * All loggers queried within `callback` (and its async descendants) will\n * automatically see these fields.\n *\n * @example\n * ```ts\n * LogixiaContext.run({ requestId: 'abc', userId: '42' }, async () => {\n * await processOrder(); // sees requestId + userId in every log\n * });\n * ```\n */\n run<T>(store: LogContext, callback: () => T): T {\n const parent = _storage.getStore() ?? {};\n return _storage.run({ ...parent, ...store }, callback);\n },\n\n /**\n * Return the context fields active in the current async scope.\n * Returns `undefined` when called outside any `LogixiaContext.run()`.\n */\n get(): LogContext | undefined {\n return _storage.getStore();\n },\n\n /**\n * Merge `fields` into the **existing** context for the current scope.\n * If called outside a `run()` context this is a no-op (logs a dev warning).\n */\n set(fields: Partial<LogContext>): void {\n const store = _storage.getStore();\n if (!store) {\n // Silently ignore — avoids crashing in code that may call setContext\n // before a request context is established (e.g. background jobs).\n return;\n }\n for (const key of Object.keys(fields)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n store[key] = fields[key];\n }\n },\n\n /**\n * Retrieve the underlying `AsyncLocalStorage` instance.\n * Useful for advanced use-cases like custom middleware.\n */\n getStorage(): AsyncLocalStorage<LogContext> {\n return _storage;\n },\n};\n\n/**\n * Create an Express/Connect-compatible middleware that wraps each request in\n * a `LogixiaContext.run()` scope populated with common request fields.\n *\n * @example\n * ```ts\n * import { createExpressContextMiddleware } from 'logixia';\n * app.use(createExpressContextMiddleware());\n * ```\n */\nexport function createExpressContextMiddleware(\n options: {\n /** Extract additional fields from the request. */\n enrich?: (req: Record<string, unknown>) => Partial<LogContext>;\n /** Header to read the traceId from. Default: 'x-trace-id'. */\n traceIdHeader?: string;\n } = {}\n) {\n const { enrich, traceIdHeader = 'x-trace-id' } = options;\n\n return function logixiaContextMiddleware(\n req: Record<string, unknown>,\n _res: unknown,\n next: () => void\n ): void {\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n const traceId = sanitizeIncomingId(headers[traceIdHeader.toLowerCase()]) ?? randomShortId();\n const base: LogContext = { traceId };\n LogixiaContext.run({ ...base, ...(enrich ? enrich(req) : {}) }, next);\n };\n}\n\n/**\n * Create a Fastify lifecycle hook that wraps each request in a context scope.\n *\n * Register with `fastify.addHook('onRequest', createFastifyContextHook())`.\n */\nexport function createFastifyContextHook(\n options: {\n enrich?: (request: Record<string, unknown>) => Partial<LogContext>;\n traceIdHeader?: string;\n } = {}\n) {\n const { enrich, traceIdHeader = 'x-trace-id' } = options;\n\n return function logixiaFastifyHook(\n request: Record<string, unknown>,\n _reply: unknown,\n done: () => void\n ): void {\n const headers = (request['headers'] ?? {}) as Record<string, string | undefined>;\n const traceId =\n sanitizeIncomingId(headers[traceIdHeader.toLowerCase()]) ??\n sanitizeIncomingId(request['id']) ??\n randomShortId();\n const base: LogContext = { traceId };\n LogixiaContext.run({ ...base, ...(enrich ? enrich(request) : {}) }, done);\n };\n}\n"],"mappings":";;;;;;AAgCA,SAAS,gBAAwB;AAC/B,QAAO,YAAY,CAAC,MAAM,GAAG,EAAE;;AAajC,MAAM,WAAW,IAAI,mBAA+B;AAEpD,MAAa,iBAAiB;CAa5B,IAAO,OAAmB,UAAsB;EAC9C,MAAM,SAAS,SAAS,UAAU,IAAI,EAAE;AACxC,SAAO,SAAS,IAAI;GAAE,GAAG;GAAQ,GAAG;GAAO,EAAE,SAAS;;CAOxD,MAA8B;AAC5B,SAAO,SAAS,UAAU;;CAO5B,IAAI,QAAmC;EACrC,MAAM,QAAQ,SAAS,UAAU;AACjC,MAAI,CAAC,MAGH;AAEF,OAAK,MAAM,OAAO,OAAO,KAAK,OAAO,EAAE;AACrC,OAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,YAAa;AACzE,SAAM,OAAO,OAAO;;;CAQxB,aAA4C;AAC1C,SAAO;;CAEV;;;;;;;;;;;AAYD,SAAgB,+BACd,UAKI,EAAE,EACN;CACA,MAAM,EAAE,QAAQ,gBAAgB,iBAAiB;AAEjD,QAAO,SAAS,yBACd,KACA,MACA,MACM;EAGN,MAAMA,OAAmB,EAAE,SADX,oBADC,IAAI,cAAc,EAAE,EACM,cAAc,aAAa,EAAE,IAAI,eAAe,EACvD;AACpC,iBAAe,IAAI;GAAE,GAAG;GAAM,GAAI,SAAS,OAAO,IAAI,GAAG,EAAE;GAAG,EAAE,KAAK;;;;;;;;AASzE,SAAgB,yBACd,UAGI,EAAE,EACN;CACA,MAAM,EAAE,QAAQ,gBAAgB,iBAAiB;AAEjD,QAAO,SAAS,mBACd,SACA,QACA,MACM;EAMN,MAAMA,OAAmB,EAAE,SAHzB,oBAFe,QAAQ,cAAc,EAAE,EAEZ,cAAc,aAAa,EAAE,IACxD,mBAAmB,QAAQ,MAAM,IACjC,eAAe,EACmB;AACpC,iBAAe,IAAI;GAAE,GAAG;GAAM,GAAI,SAAS,OAAO,QAAQ,GAAG,EAAE;GAAG,EAAE,KAAK"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-nOFOJqeH.js');
|
|
2
|
+
const require_trace_utils = require('./trace.utils-DpBOLDtq.js');
|
|
2
3
|
let node_crypto = require("node:crypto");
|
|
3
4
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
4
5
|
let node_async_hooks = require("node:async_hooks");
|
|
@@ -24,7 +25,10 @@ const LogixiaContext = {
|
|
|
24
25
|
set(fields) {
|
|
25
26
|
const store = _storage.getStore();
|
|
26
27
|
if (!store) return;
|
|
27
|
-
Object.
|
|
28
|
+
for (const key of Object.keys(fields)) {
|
|
29
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
30
|
+
store[key] = fields[key];
|
|
31
|
+
}
|
|
28
32
|
},
|
|
29
33
|
getStorage() {
|
|
30
34
|
return _storage;
|
|
@@ -43,7 +47,7 @@ const LogixiaContext = {
|
|
|
43
47
|
function createExpressContextMiddleware(options = {}) {
|
|
44
48
|
const { enrich, traceIdHeader = "x-trace-id" } = options;
|
|
45
49
|
return function logixiaContextMiddleware(req, _res, next) {
|
|
46
|
-
const base = { traceId: (req["headers"] ?? {})[traceIdHeader] ?? randomShortId() };
|
|
50
|
+
const base = { traceId: require_trace_utils.sanitizeIncomingId((req["headers"] ?? {})[traceIdHeader.toLowerCase()]) ?? randomShortId() };
|
|
47
51
|
LogixiaContext.run({
|
|
48
52
|
...base,
|
|
49
53
|
...enrich ? enrich(req) : {}
|
|
@@ -58,7 +62,7 @@ function createExpressContextMiddleware(options = {}) {
|
|
|
58
62
|
function createFastifyContextHook(options = {}) {
|
|
59
63
|
const { enrich, traceIdHeader = "x-trace-id" } = options;
|
|
60
64
|
return function logixiaFastifyHook(request, _reply, done) {
|
|
61
|
-
const base = { traceId: (request["headers"] ?? {})[traceIdHeader] ?? request["id"] ?? randomShortId() };
|
|
65
|
+
const base = { traceId: require_trace_utils.sanitizeIncomingId((request["headers"] ?? {})[traceIdHeader.toLowerCase()]) ?? require_trace_utils.sanitizeIncomingId(request["id"]) ?? randomShortId() };
|
|
62
66
|
LogixiaContext.run({
|
|
63
67
|
...base,
|
|
64
68
|
...enrich ? enrich(request) : {}
|
|
@@ -85,4 +89,4 @@ Object.defineProperty(exports, 'createFastifyContextHook', {
|
|
|
85
89
|
return createFastifyContextHook;
|
|
86
90
|
}
|
|
87
91
|
});
|
|
88
|
-
//# sourceMappingURL=async-context-
|
|
92
|
+
//# sourceMappingURL=async-context-DyQQYq3I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-context-DyQQYq3I.js","names":["AsyncLocalStorage","base: LogContext","sanitizeIncomingId"],"sources":["../src/context/async-context.ts"],"sourcesContent":["/**\n * LogixiaContext — Zero-boilerplate AsyncLocalStorage context propagation.\n *\n * Lets every `logger.info()` call inside a context automatically pick up stored\n * fields (requestId, userId, tenantId, …) without passing them through every function.\n *\n * Works across `Promise.all`, `setTimeout`, and event emitters — anything that\n * inherits the async execution context.\n *\n * @example\n * ```ts\n * import { LogixiaContext } from 'logixia';\n *\n * // Wrap a request handler:\n * app.use((req, res, next) => {\n * LogixiaContext.run({ requestId: req.id, userId: req.user?.id }, next);\n * });\n *\n * // Anywhere deeper in the call tree:\n * async function processOrder(orderId: string) {\n * const ctx = LogixiaContext.get(); // { requestId, userId, orderId }\n * logger.info('Processing order', { orderId, ...ctx });\n * }\n * ```\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { randomUUID } from 'node:crypto';\n\nimport { sanitizeIncomingId } from '../utils/trace.utils';\n\n/** Generates an 8-character random ID using Node's crypto module. */\nfunction randomShortId(): string {\n return randomUUID().slice(0, 8);\n}\n\nexport interface LogContext {\n traceId?: string;\n spanId?: string;\n userId?: string;\n tenantId?: string;\n sessionId?: string;\n [key: string]: unknown;\n}\n\n// Module-level singleton so all loggers share the same ALS store.\nconst _storage = new AsyncLocalStorage<LogContext>();\n\nexport const LogixiaContext = {\n /**\n * Run `callback` inside an async context that carries `store` fields.\n * All loggers queried within `callback` (and its async descendants) will\n * automatically see these fields.\n *\n * @example\n * ```ts\n * LogixiaContext.run({ requestId: 'abc', userId: '42' }, async () => {\n * await processOrder(); // sees requestId + userId in every log\n * });\n * ```\n */\n run<T>(store: LogContext, callback: () => T): T {\n const parent = _storage.getStore() ?? {};\n return _storage.run({ ...parent, ...store }, callback);\n },\n\n /**\n * Return the context fields active in the current async scope.\n * Returns `undefined` when called outside any `LogixiaContext.run()`.\n */\n get(): LogContext | undefined {\n return _storage.getStore();\n },\n\n /**\n * Merge `fields` into the **existing** context for the current scope.\n * If called outside a `run()` context this is a no-op (logs a dev warning).\n */\n set(fields: Partial<LogContext>): void {\n const store = _storage.getStore();\n if (!store) {\n // Silently ignore — avoids crashing in code that may call setContext\n // before a request context is established (e.g. background jobs).\n return;\n }\n for (const key of Object.keys(fields)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n store[key] = fields[key];\n }\n },\n\n /**\n * Retrieve the underlying `AsyncLocalStorage` instance.\n * Useful for advanced use-cases like custom middleware.\n */\n getStorage(): AsyncLocalStorage<LogContext> {\n return _storage;\n },\n};\n\n/**\n * Create an Express/Connect-compatible middleware that wraps each request in\n * a `LogixiaContext.run()` scope populated with common request fields.\n *\n * @example\n * ```ts\n * import { createExpressContextMiddleware } from 'logixia';\n * app.use(createExpressContextMiddleware());\n * ```\n */\nexport function createExpressContextMiddleware(\n options: {\n /** Extract additional fields from the request. */\n enrich?: (req: Record<string, unknown>) => Partial<LogContext>;\n /** Header to read the traceId from. Default: 'x-trace-id'. */\n traceIdHeader?: string;\n } = {}\n) {\n const { enrich, traceIdHeader = 'x-trace-id' } = options;\n\n return function logixiaContextMiddleware(\n req: Record<string, unknown>,\n _res: unknown,\n next: () => void\n ): void {\n const headers = (req['headers'] ?? {}) as Record<string, string | undefined>;\n const traceId = sanitizeIncomingId(headers[traceIdHeader.toLowerCase()]) ?? randomShortId();\n const base: LogContext = { traceId };\n LogixiaContext.run({ ...base, ...(enrich ? enrich(req) : {}) }, next);\n };\n}\n\n/**\n * Create a Fastify lifecycle hook that wraps each request in a context scope.\n *\n * Register with `fastify.addHook('onRequest', createFastifyContextHook())`.\n */\nexport function createFastifyContextHook(\n options: {\n enrich?: (request: Record<string, unknown>) => Partial<LogContext>;\n traceIdHeader?: string;\n } = {}\n) {\n const { enrich, traceIdHeader = 'x-trace-id' } = options;\n\n return function logixiaFastifyHook(\n request: Record<string, unknown>,\n _reply: unknown,\n done: () => void\n ): void {\n const headers = (request['headers'] ?? {}) as Record<string, string | undefined>;\n const traceId =\n sanitizeIncomingId(headers[traceIdHeader.toLowerCase()]) ??\n sanitizeIncomingId(request['id']) ??\n randomShortId();\n const base: LogContext = { traceId };\n LogixiaContext.run({ ...base, ...(enrich ? enrich(request) : {}) }, done);\n };\n}\n"],"mappings":";;;;;;;;;AAgCA,SAAS,gBAAwB;AAC/B,qCAAmB,CAAC,MAAM,GAAG,EAAE;;AAajC,MAAM,WAAW,IAAIA,oCAA+B;AAEpD,MAAa,iBAAiB;CAa5B,IAAO,OAAmB,UAAsB;EAC9C,MAAM,SAAS,SAAS,UAAU,IAAI,EAAE;AACxC,SAAO,SAAS,IAAI;GAAE,GAAG;GAAQ,GAAG;GAAO,EAAE,SAAS;;CAOxD,MAA8B;AAC5B,SAAO,SAAS,UAAU;;CAO5B,IAAI,QAAmC;EACrC,MAAM,QAAQ,SAAS,UAAU;AACjC,MAAI,CAAC,MAGH;AAEF,OAAK,MAAM,OAAO,OAAO,KAAK,OAAO,EAAE;AACrC,OAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,YAAa;AACzE,SAAM,OAAO,OAAO;;;CAQxB,aAA4C;AAC1C,SAAO;;CAEV;;;;;;;;;;;AAYD,SAAgB,+BACd,UAKI,EAAE,EACN;CACA,MAAM,EAAE,QAAQ,gBAAgB,iBAAiB;AAEjD,QAAO,SAAS,yBACd,KACA,MACA,MACM;EAGN,MAAMC,OAAmB,EAAE,SADXC,wCADC,IAAI,cAAc,EAAE,EACM,cAAc,aAAa,EAAE,IAAI,eAAe,EACvD;AACpC,iBAAe,IAAI;GAAE,GAAG;GAAM,GAAI,SAAS,OAAO,IAAI,GAAG,EAAE;GAAG,EAAE,KAAK;;;;;;;;AASzE,SAAgB,yBACd,UAGI,EAAE,EACN;CACA,MAAM,EAAE,QAAQ,gBAAgB,iBAAiB;AAEjD,QAAO,SAAS,mBACd,SACA,QACA,MACM;EAMN,MAAMD,OAAmB,EAAE,SAHzBC,wCAFe,QAAQ,cAAc,EAAE,EAEZ,cAAc,aAAa,EAAE,IACxDA,uCAAmB,QAAQ,MAAM,IACjC,eAAe,EACmB;AACpC,iBAAe,IAAI;GAAE,GAAG;GAAM,GAAI,SAAS,OAAO,QAAQ,GAAG,EAAE;GAAG,EAAE,KAAK"}
|
package/dist/{azure-monitor.transport-CG7ClkYj.d.ts → azure-monitor.transport-BDFO4F-i.d.ts}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-
|
|
1
|
+
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-DqVLZTyf.js";
|
|
2
2
|
|
|
3
3
|
//#region src/transports/azure-monitor.transport.d.ts
|
|
4
4
|
|
|
@@ -59,4 +59,4 @@ declare class AzureMonitorTransport implements IAsyncTransport {
|
|
|
59
59
|
}
|
|
60
60
|
//#endregion
|
|
61
61
|
export { AzureMonitorTransportConfig as n, AzureMonitorTransport as t };
|
|
62
|
-
//# sourceMappingURL=azure-monitor.transport-
|
|
62
|
+
//# sourceMappingURL=azure-monitor.transport-BDFO4F-i.d.ts.map
|
package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-BDFO4F-i.d.ts.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"azure-monitor.transport-
|
|
1
|
+
{"version":3,"file":"azure-monitor.transport-BDFO4F-i.d.ts","names":[],"sources":["../src/transports/azure-monitor.transport.ts"],"sourcesContent":[],"mappings":";;;;UAkDiB,2BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;cAwCJ,qBAAA,YAAiC;;;;;;;;;;;;;;;;sBAmBxB;eAwBP;WAKJ;;;;;;WASM"}
|
package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D5wQEH04.d.mts}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-
|
|
1
|
+
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-CtNr3D7S.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/transports/azure-monitor.transport.d.ts
|
|
4
4
|
|
|
@@ -59,4 +59,4 @@ declare class AzureMonitorTransport implements IAsyncTransport {
|
|
|
59
59
|
}
|
|
60
60
|
//#endregion
|
|
61
61
|
export { AzureMonitorTransportConfig as n, AzureMonitorTransport as t };
|
|
62
|
-
//# sourceMappingURL=azure-monitor.transport-
|
|
62
|
+
//# sourceMappingURL=azure-monitor.transport-D5wQEH04.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"azure-monitor.transport-
|
|
1
|
+
{"version":3,"file":"azure-monitor.transport-D5wQEH04.d.mts","names":[],"sources":["../src/transports/azure-monitor.transport.ts"],"sourcesContent":[],"mappings":";;;;UAkDiB,2BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;cAwCJ,qBAAA,YAAiC;;;;;;;;;;;;;;;;sBAmBxB;eAwBP;WAKJ;;;;;;WASM"}
|
package/dist/azure.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./transport.types-
|
|
2
|
-
import { n as AzureMonitorTransportConfig, t as AzureMonitorTransport } from "./azure-monitor.transport-
|
|
1
|
+
import "./transport.types-CtNr3D7S.mjs";
|
|
2
|
+
import { n as AzureMonitorTransportConfig, t as AzureMonitorTransport } from "./azure-monitor.transport-D5wQEH04.mjs";
|
|
3
3
|
export { AzureMonitorTransport, AzureMonitorTransportConfig };
|
package/dist/azure.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./transport.types-
|
|
2
|
-
import { n as AzureMonitorTransportConfig, t as AzureMonitorTransport } from "./azure-monitor.transport-
|
|
1
|
+
import "./transport.types-DqVLZTyf.js";
|
|
2
|
+
import { n as AzureMonitorTransportConfig, t as AzureMonitorTransport } from "./azure-monitor.transport-BDFO4F-i.js";
|
|
3
3
|
export { AzureMonitorTransport, AzureMonitorTransportConfig };
|
package/dist/cli/index.js
CHANGED
|
@@ -31,6 +31,25 @@ commander = __toESM(commander);
|
|
|
31
31
|
let picocolors = require("picocolors");
|
|
32
32
|
picocolors = __toESM(picocolors);
|
|
33
33
|
|
|
34
|
+
//#region src/utils/csv.utils.ts
|
|
35
|
+
const CSV_FORMULA_PREFIX = /^[=+\-@\t\r]/;
|
|
36
|
+
/**
|
|
37
|
+
* Render one CSV field. String values that a spreadsheet would treat as a
|
|
38
|
+
* formula (leading `=`, `+`, `-`, `@`, tab or CR) are prefixed with `'`.
|
|
39
|
+
* Objects are JSON-encoded. Fields containing a comma, quote, CR or LF are quoted;
|
|
40
|
+
* `alwaysQuote` quotes every field. `null` / `undefined` render as empty.
|
|
41
|
+
*/
|
|
42
|
+
function csvField(value, alwaysQuote = false) {
|
|
43
|
+
if (value === void 0 || value === null) return alwaysQuote ? "\"\"" : "";
|
|
44
|
+
let str;
|
|
45
|
+
if (typeof value === "string") str = CSV_FORMULA_PREFIX.test(value) ? `'${value}` : value;
|
|
46
|
+
else if (typeof value === "object") str = JSON.stringify(value);
|
|
47
|
+
else str = String(value);
|
|
48
|
+
if (alwaysQuote || /[",\r\n]/.test(str)) return `"${str.replace(/"/g, "\"\"")}"`;
|
|
49
|
+
return str;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
34
53
|
//#region src/cli/utils.ts
|
|
35
54
|
/**
|
|
36
55
|
* Render a single cell value. Uses a null/undefined check rather than `||` so
|
|
@@ -38,12 +57,33 @@ picocolors = __toESM(picocolors);
|
|
|
38
57
|
* `statusCode=0` field must not silently display as blank.
|
|
39
58
|
*/
|
|
40
59
|
function cellText(value) {
|
|
41
|
-
return value === null || value === void 0 ? "" : String(value);
|
|
60
|
+
return value === null || value === void 0 ? "" : sanitizeInline(String(value));
|
|
61
|
+
}
|
|
62
|
+
const TERMINAL_UNSAFE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u2028\u2029]/g;
|
|
63
|
+
function escapeChar(ch) {
|
|
64
|
+
const code = ch.charCodeAt(0);
|
|
65
|
+
return code <= 255 ? `\\x${code.toString(16).padStart(2, "0")}` : `\\u${code.toString(16).padStart(4, "0")}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Make untrusted log text safe to print: control characters (ESC, CR, BEL, C1
|
|
69
|
+
* codes, ...) are replaced by a visible `\xNN` escape. Tabs and newlines are kept
|
|
70
|
+
* so multi-line output the CLI formats itself is unaffected.
|
|
71
|
+
*/
|
|
72
|
+
function sanitizeForTerminal(text) {
|
|
73
|
+
return text.replace(TERMINAL_UNSAFE, escapeChar);
|
|
74
|
+
}
|
|
75
|
+
/** Like {@link sanitizeForTerminal} but also escapes newlines and tabs (single-line cells). */
|
|
76
|
+
function sanitizeInline(text) {
|
|
77
|
+
return sanitizeForTerminal(text).replace(/\n/g, "\\n").replace(/\t/g, "\\t");
|
|
78
|
+
}
|
|
79
|
+
/** Escape regular-expression metacharacters so `text` matches literally. */
|
|
80
|
+
function escapeRegExp(text) {
|
|
81
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42
82
|
}
|
|
43
83
|
function formatAsTable(rows, columns) {
|
|
44
|
-
const colWidths = columns.map((c) => Math.max(c.length, ...rows.map((r) => cellText(r[c]).length)));
|
|
84
|
+
const colWidths = columns.map((c) => Math.max(sanitizeInline(c).length, ...rows.map((r) => cellText(r[c]).length)));
|
|
45
85
|
return [
|
|
46
|
-
columns.map((c, i) => c.padEnd(colWidths[i] ?? 0)).join(" | "),
|
|
86
|
+
columns.map((c, i) => sanitizeInline(c).padEnd(colWidths[i] ?? 0)).join(" | "),
|
|
47
87
|
colWidths.map((w) => "-".repeat(w)).join("-|-"),
|
|
48
88
|
rows.map((r) => columns.map((c, i) => cellText(r[c]).padEnd(colWidths[i] ?? 0)).join(" | ")).join("\n")
|
|
49
89
|
].join("\n");
|
|
@@ -90,7 +130,7 @@ async function analyzeFileContents(raw, opts = {}) {
|
|
|
90
130
|
});
|
|
91
131
|
}
|
|
92
132
|
if (opts.level) filtered = filtered.filter((e) => (e["level"] ?? "").toLowerCase() === opts.level.toLowerCase());
|
|
93
|
-
const byLevel =
|
|
133
|
+
const byLevel = Object.create(null);
|
|
94
134
|
for (const e of filtered) {
|
|
95
135
|
const l = (e.level || "info").toUpperCase();
|
|
96
136
|
byLevel[l] = (byLevel[l] || 0) + 1;
|
|
@@ -112,7 +152,7 @@ const analyzeCommand = new commander.Command("analyze").description("Analyze log
|
|
|
112
152
|
}
|
|
113
153
|
const result = await analyzeFileContents(node_fs.default.readFileSync(full, "utf8"), opts);
|
|
114
154
|
if (opts.format === "json") {
|
|
115
|
-
console.log(JSON.stringify(result, null, 2));
|
|
155
|
+
console.log(sanitizeForTerminal(JSON.stringify(result, null, 2)));
|
|
116
156
|
return;
|
|
117
157
|
}
|
|
118
158
|
console.log(result);
|
|
@@ -178,10 +218,10 @@ function formatTime(ts) {
|
|
|
178
218
|
if (!ts) return " ";
|
|
179
219
|
try {
|
|
180
220
|
const d = new Date(String(ts));
|
|
181
|
-
if (Number.isNaN(d.getTime())) return String(ts).slice(0, 12).padEnd(12);
|
|
221
|
+
if (Number.isNaN(d.getTime())) return sanitizeInline(String(ts).slice(0, 12)).padEnd(12);
|
|
182
222
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}.${String(d.getMilliseconds()).padStart(3, "0")}`;
|
|
183
223
|
} catch {
|
|
184
|
-
return String(ts).slice(0, 12).padEnd(12);
|
|
224
|
+
return sanitizeInline(String(ts).slice(0, 12)).padEnd(12);
|
|
185
225
|
}
|
|
186
226
|
}
|
|
187
227
|
function syntaxColorJson(line) {
|
|
@@ -204,12 +244,12 @@ function buildDetailLines(entry) {
|
|
|
204
244
|
if (!entry) return [picocolors.default.dim(" No entry selected")];
|
|
205
245
|
const lines = [];
|
|
206
246
|
const { stack,...rest } = entry;
|
|
207
|
-
const jsonText = JSON.stringify(rest, null, 2);
|
|
247
|
+
const jsonText = sanitizeForTerminal(JSON.stringify(rest, null, 2));
|
|
208
248
|
for (const l of jsonText.split("\n")) lines.push(" " + syntaxColorJson(l));
|
|
209
249
|
if (stack) {
|
|
210
250
|
lines.push("");
|
|
211
251
|
lines.push(picocolors.default.bold(picocolors.default.red(" ▼ STACK TRACE")));
|
|
212
|
-
for (const sl of String(stack).split("\n")) {
|
|
252
|
+
for (const sl of sanitizeForTerminal(String(stack)).split("\n")) {
|
|
213
253
|
if (!sl.trim()) continue;
|
|
214
254
|
if (sl.trim().startsWith("at ")) lines.push(coloriseStackFrame(sl));
|
|
215
255
|
else lines.push(" " + picocolors.default.red(sl.trim()));
|
|
@@ -367,18 +407,18 @@ var TUIExplorer = class {
|
|
|
367
407
|
}
|
|
368
408
|
const entry = this.filteredEntries[entryIdx];
|
|
369
409
|
const isSelected = entryIdx === this.selectedIndex;
|
|
370
|
-
const level = normalizeLevel(entry.level);
|
|
410
|
+
const level = sanitizeInline(normalizeLevel(entry.level));
|
|
371
411
|
const time = formatTime(entry.timestamp);
|
|
372
|
-
const msg = String(entry.message ?? "");
|
|
373
|
-
const
|
|
412
|
+
const msg = sanitizeInline(String(entry.message ?? ""));
|
|
413
|
+
const extraFieldsSafe = sanitizeInline(Object.entries(entry).filter(([k]) => ![
|
|
374
414
|
"timestamp",
|
|
375
415
|
"level",
|
|
376
416
|
"message",
|
|
377
417
|
"stack"
|
|
378
|
-
].includes(k)).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : String(v)}`).join(" ");
|
|
418
|
+
].includes(k)).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : String(v)}`).join(" "));
|
|
379
419
|
const msgSlot = Math.floor(msgWidth * .65);
|
|
380
420
|
const msgPart = this.truncate(msg, msgSlot);
|
|
381
|
-
const extraPart = this.truncate(
|
|
421
|
+
const extraPart = this.truncate(extraFieldsSafe, msgWidth - msgPart.length - 2);
|
|
382
422
|
const msgDisplay = msgPart + (extraPart ? picocolors.default.dim(" " + extraPart) : "");
|
|
383
423
|
const line = ` ${time} ` + levelBadge(level) + " " + this.pad(msgDisplay, msgWidth);
|
|
384
424
|
if (isSelected) {
|
|
@@ -550,10 +590,10 @@ var TUIExplorer = class {
|
|
|
550
590
|
if (ext === ".csv") {
|
|
551
591
|
const entries = this.filteredEntries;
|
|
552
592
|
const allKeys = [...new Set(entries.flatMap((e) => Object.keys(e)))];
|
|
553
|
-
const header = allKeys.join(",");
|
|
593
|
+
const header = allKeys.map((k) => csvField(k)).join(",");
|
|
554
594
|
const rows = entries.map((e) => allKeys.map((k) => {
|
|
555
595
|
const v = e[k];
|
|
556
|
-
return
|
|
596
|
+
return csvField(v, true);
|
|
557
597
|
}).join(","));
|
|
558
598
|
node_fs.default.writeFileSync(resolved, [header, ...rows].join("\n"), "utf8");
|
|
559
599
|
} else if (ext === ".ndjson" || ext === ".jsonl") node_fs.default.writeFileSync(resolved, this.filteredEntries.map((e) => JSON.stringify(e)).join("\n"), "utf8");
|
|
@@ -653,15 +693,9 @@ function exportAsCSV(entries, fields) {
|
|
|
653
693
|
if (entries.length === 0) return "";
|
|
654
694
|
const columns = fields || Object.keys(entries[0] || {});
|
|
655
695
|
const lines = [];
|
|
656
|
-
lines.push(columns.join(","));
|
|
696
|
+
lines.push(columns.map((c) => csvField(c)).join(","));
|
|
657
697
|
for (const entry of entries) {
|
|
658
|
-
const row = columns.map((col) =>
|
|
659
|
-
const value = entry[col];
|
|
660
|
-
if (value === void 0 || value === null) return "";
|
|
661
|
-
const str = String(value);
|
|
662
|
-
if (str.includes(",") || str.includes("\"") || str.includes("\n")) return `"${str.replace(/"/g, "\"\"")}"`;
|
|
663
|
-
return str;
|
|
664
|
-
});
|
|
698
|
+
const row = columns.map((col) => csvField(entry[col]));
|
|
665
699
|
lines.push(row.join(","));
|
|
666
700
|
}
|
|
667
701
|
return lines.join("\n");
|
|
@@ -922,6 +956,21 @@ function parseCondition(expr) {
|
|
|
922
956
|
}
|
|
923
957
|
return null;
|
|
924
958
|
}
|
|
959
|
+
/** Build a case-insensitive regex for a SQL LIKE pattern; only `%` and `_` are special. */
|
|
960
|
+
function likeToRegExp(pattern) {
|
|
961
|
+
let source = "";
|
|
962
|
+
let lastWasWildcard = false;
|
|
963
|
+
for (const ch of pattern) {
|
|
964
|
+
if (ch === "%") {
|
|
965
|
+
if (!lastWasWildcard) source += ".*";
|
|
966
|
+
lastWasWildcard = true;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
lastWasWildcard = false;
|
|
970
|
+
source += ch === "_" ? "." : escapeRegExp(ch);
|
|
971
|
+
}
|
|
972
|
+
return new RegExp(`^${source}$`, "i");
|
|
973
|
+
}
|
|
925
974
|
function matchesCondition(row, cond) {
|
|
926
975
|
const rawFieldValue = row[cond.field];
|
|
927
976
|
if (rawFieldValue === void 0) return false;
|
|
@@ -934,14 +983,8 @@ function matchesCondition(row, cond) {
|
|
|
934
983
|
case ">=": return Number(rawFieldValue) >= Number(cond.value);
|
|
935
984
|
case "<": return Number(rawFieldValue) < Number(cond.value);
|
|
936
985
|
case "<=": return Number(rawFieldValue) <= Number(cond.value);
|
|
937
|
-
case "LIKE":
|
|
938
|
-
|
|
939
|
-
return new RegExp(`^${pattern}$`, "i").test(fieldStr);
|
|
940
|
-
}
|
|
941
|
-
case "NOT LIKE": {
|
|
942
|
-
const pattern = condStr.replace(/%/g, ".*").replace(/_/g, ".");
|
|
943
|
-
return !new RegExp(`^${pattern}$`, "i").test(fieldStr);
|
|
944
|
-
}
|
|
986
|
+
case "LIKE": return likeToRegExp(condStr).test(fieldStr);
|
|
987
|
+
case "NOT LIKE": return !likeToRegExp(condStr).test(fieldStr);
|
|
945
988
|
case "IN": return cond.value.some((v) => String(rawFieldValue) === v);
|
|
946
989
|
case "NOT IN": return !cond.value.some((v) => String(rawFieldValue) === v);
|
|
947
990
|
default: return false;
|
|
@@ -961,18 +1004,19 @@ function matchesNode(row, node) {
|
|
|
961
1004
|
function runAggregation(entries, agg) {
|
|
962
1005
|
const { fn, field, groupBy } = agg;
|
|
963
1006
|
if (!groupBy) return picocolors.default.yellow("Aggregation requires a GROUP BY field");
|
|
964
|
-
const groups =
|
|
1007
|
+
const groups = /* @__PURE__ */ new Map();
|
|
965
1008
|
for (const entry of entries) {
|
|
966
1009
|
const key = String(entry[groupBy] ?? "(empty)");
|
|
967
|
-
|
|
968
|
-
|
|
1010
|
+
const bucket = groups.get(key);
|
|
1011
|
+
if (bucket) bucket.push(entry);
|
|
1012
|
+
else groups.set(key, [entry]);
|
|
969
1013
|
}
|
|
970
|
-
if (fn === "COUNT" || fn === "GROUP") return formatAsTable(
|
|
1014
|
+
if (fn === "COUNT" || fn === "GROUP") return formatAsTable([...groups.entries()].map(([k, v]) => ({
|
|
971
1015
|
[groupBy]: k,
|
|
972
1016
|
count: v.length
|
|
973
1017
|
})).sort((a, b) => b.count - a.count), [groupBy, "count"]);
|
|
974
1018
|
if (!field) return picocolors.default.yellow("Numeric aggregation requires a field: AVG(field) BY group");
|
|
975
|
-
return formatAsTable(
|
|
1019
|
+
return formatAsTable([...groups.entries()].map(([k, v]) => {
|
|
976
1020
|
const nums = v.map((e) => Number(e[field])).filter((n) => !Number.isNaN(n));
|
|
977
1021
|
let result;
|
|
978
1022
|
switch (fn) {
|
|
@@ -1012,12 +1056,12 @@ function projectRow(row, select) {
|
|
|
1012
1056
|
}
|
|
1013
1057
|
function formatResults(results, format) {
|
|
1014
1058
|
if (results.length === 0) return picocolors.default.yellow("No results found");
|
|
1015
|
-
if (format === "json") return JSON.stringify(results, null, 2);
|
|
1016
|
-
if (format === "ndjson") return results.map((r) => JSON.stringify(r)).join("\n");
|
|
1059
|
+
if (format === "json") return sanitizeForTerminal(JSON.stringify(results, null, 2));
|
|
1060
|
+
if (format === "ndjson") return results.map((r) => sanitizeForTerminal(JSON.stringify(r))).join("\n");
|
|
1017
1061
|
if (format === "table") return formatAsTable(results, Object.keys(results[0] || {}).slice(0, 6));
|
|
1018
1062
|
return results.map((r) => {
|
|
1019
1063
|
const level = (r.level || "").toUpperCase();
|
|
1020
|
-
const line = JSON.stringify(r);
|
|
1064
|
+
const line = sanitizeForTerminal(JSON.stringify(r));
|
|
1021
1065
|
if (level === "ERROR" || level === "CRITICAL") return picocolors.default.red(line);
|
|
1022
1066
|
if (level === "WARN") return picocolors.default.yellow(line);
|
|
1023
1067
|
if (level === "DEBUG" || level === "VERBOSE") return picocolors.default.gray(line);
|
|
@@ -1115,7 +1159,7 @@ const queryCommand = new commander.Command("query").description("Query log files
|
|
|
1115
1159
|
});
|
|
1116
1160
|
if (tailResults.length > 0) console.log(formatResults(tailResults, opts.format || "line"));
|
|
1117
1161
|
} catch {
|
|
1118
|
-
if (!opts.sql) console.log(line);
|
|
1162
|
+
if (!opts.sql) console.log(sanitizeForTerminal(line));
|
|
1119
1163
|
}
|
|
1120
1164
|
});
|
|
1121
1165
|
rs.on("end", () => {
|
|
@@ -1148,14 +1192,14 @@ async function searchLogs(raw, opts = {}) {
|
|
|
1148
1192
|
});
|
|
1149
1193
|
}
|
|
1150
1194
|
function formatSearchResults(results, format, _context) {
|
|
1151
|
-
if (format === "json") return JSON.stringify(results, null, 2);
|
|
1195
|
+
if (format === "json") return sanitizeForTerminal(JSON.stringify(results, null, 2));
|
|
1152
1196
|
if (format === "table") {
|
|
1153
1197
|
if (results.length === 0) return picocolors.default.yellow("No results found");
|
|
1154
1198
|
return formatAsTable(results, Object.keys(results[0] || {}).slice(0, 4));
|
|
1155
1199
|
}
|
|
1156
1200
|
if (results.length === 0) return picocolors.default.yellow("No results found");
|
|
1157
1201
|
return results.map((r, idx) => {
|
|
1158
|
-
const line = JSON.stringify(r);
|
|
1202
|
+
const line = sanitizeForTerminal(JSON.stringify(r));
|
|
1159
1203
|
return `${picocolors.default.gray(String(idx + 1) + ":")} ${line}`;
|
|
1160
1204
|
}).join("\n");
|
|
1161
1205
|
}
|
|
@@ -1179,11 +1223,11 @@ const searchCommand = new commander.Command("search").description("Search log fi
|
|
|
1179
1223
|
async function generateStats(raw, opts = {}) {
|
|
1180
1224
|
const entries = safeParseLogs(raw);
|
|
1181
1225
|
const group = opts.groupBy || "level";
|
|
1182
|
-
const agg =
|
|
1226
|
+
const agg = Object.create(null);
|
|
1183
1227
|
let minTime = null;
|
|
1184
1228
|
let maxTime = null;
|
|
1185
1229
|
for (const e of entries) {
|
|
1186
|
-
const key = (e[group] || e.level || "unknown").
|
|
1230
|
+
const key = String(e[group] || e.level || "unknown").toUpperCase();
|
|
1187
1231
|
agg[key] = (agg[key] || 0) + 1;
|
|
1188
1232
|
if (e.timestamp) {
|
|
1189
1233
|
const ts = new Date(e.timestamp);
|
|
@@ -1204,14 +1248,15 @@ async function generateStats(raw, opts = {}) {
|
|
|
1204
1248
|
function formatStatsOutput(stats, filename) {
|
|
1205
1249
|
const s = stats;
|
|
1206
1250
|
const lines = [];
|
|
1207
|
-
lines.push(picocolors.default.bold(`\nLog Statistics for ${node_path.default.basename(filename)}`));
|
|
1251
|
+
lines.push(picocolors.default.bold(`\nLog Statistics for ${sanitizeInline(node_path.default.basename(filename))}`));
|
|
1208
1252
|
lines.push(picocolors.default.bold("=".repeat(50)));
|
|
1209
1253
|
lines.push(picocolors.default.cyan(`Total Entries: ${s["total"].toLocaleString()}`));
|
|
1210
1254
|
if (s["timeRange"]) lines.push(picocolors.default.cyan(`Time Range: ${s["timeRange"]["start"].toISOString()} - ${s["timeRange"]["end"].toISOString()}`));
|
|
1211
1255
|
lines.push("");
|
|
1212
|
-
lines.push(picocolors.default.bold(`${s["groupBy"].charAt(0).toUpperCase() + s["groupBy"].slice(1)} Distribution:`));
|
|
1256
|
+
lines.push(picocolors.default.bold(`${sanitizeInline(s["groupBy"].charAt(0).toUpperCase() + s["groupBy"].slice(1))} Distribution:`));
|
|
1213
1257
|
const sorted = Object.entries(s["distribution"]).sort(([, a], [, b]) => b - a);
|
|
1214
|
-
for (const [
|
|
1258
|
+
for (const [rawKey, count] of sorted) {
|
|
1259
|
+
const key = sanitizeInline(rawKey);
|
|
1215
1260
|
const percentage = (count / s["total"] * 100).toFixed(1);
|
|
1216
1261
|
const bar = "█".repeat(Math.floor(count / s["total"] * 30));
|
|
1217
1262
|
let colorFn = picocolors.default.white;
|
|
@@ -1231,7 +1276,7 @@ const statsCommand = new commander.Command("stats").description("Show statistics
|
|
|
1231
1276
|
process.exit(2);
|
|
1232
1277
|
}
|
|
1233
1278
|
const stats = await generateStats(node_fs.default.readFileSync(full, "utf8"), opts);
|
|
1234
|
-
if (opts.format === "json") console.log(JSON.stringify(stats, null, 2));
|
|
1279
|
+
if (opts.format === "json") console.log(sanitizeForTerminal(JSON.stringify(stats, null, 2)));
|
|
1235
1280
|
else console.log(formatStatsOutput(stats, file));
|
|
1236
1281
|
});
|
|
1237
1282
|
|
|
@@ -1256,17 +1301,18 @@ function applyFilter(line, filter) {
|
|
|
1256
1301
|
return line.toLowerCase().includes(filter.value.toLowerCase());
|
|
1257
1302
|
}
|
|
1258
1303
|
}
|
|
1259
|
-
function highlightLine(
|
|
1304
|
+
function highlightLine(rawLine, highlight) {
|
|
1305
|
+
const line = sanitizeForTerminal(rawLine);
|
|
1260
1306
|
if (!highlight) return line;
|
|
1261
1307
|
try {
|
|
1262
|
-
const level = (JSON.parse(
|
|
1308
|
+
const level = (JSON.parse(rawLine).level || "").toUpperCase();
|
|
1263
1309
|
if (level === "ERROR") return picocolors.default.red(line);
|
|
1264
1310
|
if (level === "WARN") return picocolors.default.yellow(line);
|
|
1265
1311
|
if (level === "INFO") return picocolors.default.blue(line);
|
|
1266
1312
|
if (level === "DEBUG") return picocolors.default.gray(line);
|
|
1267
1313
|
return line;
|
|
1268
1314
|
} catch {
|
|
1269
|
-
const regex = new RegExp(highlight, "gi");
|
|
1315
|
+
const regex = new RegExp(escapeRegExp(sanitizeForTerminal(highlight)), "gi");
|
|
1270
1316
|
return line.replace(regex, (match) => picocolors.default.bgYellow(picocolors.default.black(match)));
|
|
1271
1317
|
}
|
|
1272
1318
|
}
|
package/dist/cloudwatch.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./transport.types-
|
|
2
|
-
import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-
|
|
1
|
+
import "./transport.types-CtNr3D7S.mjs";
|
|
2
|
+
import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-Blc7Gtxt.mjs";
|
|
3
3
|
export { CloudWatchTransport, CloudWatchTransportConfig };
|
package/dist/cloudwatch.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./transport.types-
|
|
2
|
-
import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-
|
|
1
|
+
import "./transport.types-DqVLZTyf.js";
|
|
2
|
+
import { n as CloudWatchTransportConfig, t as CloudWatchTransport } from "./cloudwatch.transport-B00st6Xv.js";
|
|
3
3
|
export { CloudWatchTransport, CloudWatchTransportConfig };
|
package/dist/cloudwatch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require('./internal-log-BUJYMZbE.js');
|
|
2
2
|
require('./safe-stringify-DWo3aZp_.js');
|
|
3
3
|
require('./batch-buffer-BY-mRWIK.js');
|
|
4
|
-
const require_cloudwatch_transport = require('./cloudwatch.transport-
|
|
4
|
+
const require_cloudwatch_transport = require('./cloudwatch.transport-BO1ahxw-.js');
|
|
5
|
+
require('./entry-fields-BvOLFlJ4.js');
|
|
5
6
|
|
|
6
7
|
exports.CloudWatchTransport = require_cloudwatch_transport.CloudWatchTransport;
|
package/dist/cloudwatch.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "./internal-log-Cm8SOWSV.mjs";
|
|
2
2
|
import "./safe-stringify-DJ66tW5I.mjs";
|
|
3
3
|
import "./batch-buffer-PGvSgbmd.mjs";
|
|
4
|
-
import { t as CloudWatchTransport } from "./cloudwatch.transport-
|
|
4
|
+
import { t as CloudWatchTransport } from "./cloudwatch.transport-DZ6ies7g.mjs";
|
|
5
|
+
import "./entry-fields-BJkuPnIg.mjs";
|
|
5
6
|
|
|
6
7
|
export { CloudWatchTransport };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-
|
|
1
|
+
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-DqVLZTyf.js";
|
|
2
2
|
|
|
3
3
|
//#region src/transports/cloudwatch.transport.d.ts
|
|
4
4
|
|
|
@@ -36,6 +36,12 @@ interface CloudWatchTransportConfig {
|
|
|
36
36
|
emf?: boolean;
|
|
37
37
|
/** CloudWatch Metrics namespace used by EMF. @default 'Logixia' */
|
|
38
38
|
emfNamespace?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Payload fields published as EMF metrics. When set, only these numeric
|
|
41
|
+
* fields become metrics; when unset every numeric payload field does (up to
|
|
42
|
+
* the EMF limit of 100 per entry).
|
|
43
|
+
*/
|
|
44
|
+
emfMetrics?: string[];
|
|
39
45
|
/** Abort a request that has not finished after this many ms. @default 10000 */
|
|
40
46
|
requestTimeoutMs?: number;
|
|
41
47
|
/** Max buffered events; the oldest are dropped past this. @default 10000 */
|
|
@@ -62,6 +68,7 @@ declare class CloudWatchTransport implements IAsyncTransport {
|
|
|
62
68
|
private readonly logStreamName;
|
|
63
69
|
private readonly emf;
|
|
64
70
|
private readonly emfNamespace;
|
|
71
|
+
private readonly emfMetrics;
|
|
65
72
|
constructor(config: CloudWatchTransportConfig);
|
|
66
73
|
write(entry: TransportLogEntry): void;
|
|
67
74
|
flush(): Promise<void>;
|
|
@@ -78,4 +85,4 @@ declare class CloudWatchTransport implements IAsyncTransport {
|
|
|
78
85
|
}
|
|
79
86
|
//#endregion
|
|
80
87
|
export { CloudWatchTransportConfig as n, CloudWatchTransport as t };
|
|
81
|
-
//# sourceMappingURL=cloudwatch.transport-
|
|
88
|
+
//# sourceMappingURL=cloudwatch.transport-B00st6Xv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudwatch.transport-B00st6Xv.d.ts","names":[],"sources":["../src/transports/cloudwatch.transport.ts"],"sourcesContent":[],"mappings":";;;;UA+CiB,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+EJ,mBAAA,YAA+B;;;;;;;;;;;;;;;;;sBAoBtB;eAiCP;WAOJ;;;;;;WASM"}
|