@nimbus-cqrs/core 2.1.2 → 2.3.0
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/esm/_dnt.polyfills.d.ts +7 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +1 -0
- package/esm/index.d.ts +4 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +4 -0
- package/esm/lib/env/getEnv.d.ts +15 -0
- package/esm/lib/env/getEnv.d.ts.map +1 -0
- package/esm/lib/env/getEnv.js +35 -0
- package/esm/lib/eventBus/eventBus.d.ts +0 -1
- package/esm/lib/eventBus/eventBus.d.ts.map +1 -1
- package/esm/lib/eventBus/eventBus.js +31 -32
- package/esm/lib/exception/exception.d.ts +1 -1
- package/esm/lib/exception/exception.d.ts.map +1 -1
- package/esm/lib/exception/invalidInputException.d.ts +1 -1
- package/esm/lib/exception/invalidInputException.d.ts.map +1 -1
- package/esm/lib/log/logTruncator.d.ts +96 -0
- package/esm/lib/log/logTruncator.d.ts.map +1 -0
- package/esm/lib/log/logTruncator.js +254 -0
- package/esm/lib/log/logger.d.ts +20 -0
- package/esm/lib/log/logger.d.ts.map +1 -1
- package/esm/lib/log/logger.js +28 -1
- package/esm/lib/log/options.d.ts +13 -1
- package/esm/lib/log/options.d.ts.map +1 -1
- package/esm/lib/message/command.d.ts +11 -1
- package/esm/lib/message/command.d.ts.map +1 -1
- package/esm/lib/message/command.js +17 -10
- package/esm/lib/message/event.d.ts +11 -1
- package/esm/lib/message/event.d.ts.map +1 -1
- package/esm/lib/message/event.js +17 -10
- package/esm/lib/message/message.d.ts +14 -0
- package/esm/lib/message/message.d.ts.map +1 -1
- package/esm/lib/message/message.js +47 -1
- package/esm/lib/message/query.d.ts +11 -1
- package/esm/lib/message/query.d.ts.map +1 -1
- package/esm/lib/message/query.js +16 -9
- package/esm/lib/retry/withRetry.d.ts +175 -0
- package/esm/lib/retry/withRetry.d.ts.map +1 -0
- package/esm/lib/retry/withRetry.js +207 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,KAAK;QACb,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CACF;AAED,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "./_dnt.polyfills.js";
|
|
2
|
+
export * from './lib/env/getEnv.js';
|
|
1
3
|
export * from './lib/eventBus/eventBus.js';
|
|
2
4
|
export * from './lib/exception/exception.js';
|
|
3
5
|
export * from './lib/exception/forbiddenException.js';
|
|
@@ -6,6 +8,7 @@ export * from './lib/exception/invalidInputException.js';
|
|
|
6
8
|
export * from './lib/exception/notFoundException.js';
|
|
7
9
|
export * from './lib/exception/unauthorizedException.js';
|
|
8
10
|
export * from './lib/log/logFormatter.js';
|
|
11
|
+
export * from './lib/log/logTruncator.js';
|
|
9
12
|
export * from './lib/log/logger.js';
|
|
10
13
|
export * from './lib/log/logLevel.js';
|
|
11
14
|
export * from './lib/log/options.js';
|
|
@@ -14,5 +17,6 @@ export * from './lib/message/event.js';
|
|
|
14
17
|
export * from './lib/message/message.js';
|
|
15
18
|
export * from './lib/message/query.js';
|
|
16
19
|
export * from './lib/message/router.js';
|
|
20
|
+
export * from './lib/retry/withRetry.js';
|
|
17
21
|
export * from './lib/tracing/withSpan.js';
|
|
18
22
|
//# sourceMappingURL=index.d.ts.map
|
package/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,0CAA0C,CAAC;AACzD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,0CAA0C,CAAC;AACzD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC"}
|
package/esm/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "./_dnt.polyfills.js";
|
|
2
|
+
export * from './lib/env/getEnv.js';
|
|
1
3
|
export * from './lib/eventBus/eventBus.js';
|
|
2
4
|
export * from './lib/exception/exception.js';
|
|
3
5
|
export * from './lib/exception/forbiddenException.js';
|
|
@@ -6,6 +8,7 @@ export * from './lib/exception/invalidInputException.js';
|
|
|
6
8
|
export * from './lib/exception/notFoundException.js';
|
|
7
9
|
export * from './lib/exception/unauthorizedException.js';
|
|
8
10
|
export * from './lib/log/logFormatter.js';
|
|
11
|
+
export * from './lib/log/logTruncator.js';
|
|
9
12
|
export * from './lib/log/logger.js';
|
|
10
13
|
export * from './lib/log/logLevel.js';
|
|
11
14
|
export * from './lib/log/options.js';
|
|
@@ -14,4 +17,5 @@ export * from './lib/message/event.js';
|
|
|
14
17
|
export * from './lib/message/message.js';
|
|
15
18
|
export * from './lib/message/query.js';
|
|
16
19
|
export * from './lib/message/router.js';
|
|
20
|
+
export * from './lib/retry/withRetry.js';
|
|
17
21
|
export * from './lib/tracing/withSpan.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type GetEnvInput = {
|
|
2
|
+
variables: string[];
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Get environment variables from the process.env object.
|
|
6
|
+
* Throws an exception if any of the variables are not defined
|
|
7
|
+
* and logs the missing variable names.
|
|
8
|
+
*
|
|
9
|
+
* @param variables - The list of environment variables to get
|
|
10
|
+
* @returns {Record<string, string>} Object of the environment variables
|
|
11
|
+
* @throws {GenericException} Thrown if any of the requested variables are not defined
|
|
12
|
+
*/
|
|
13
|
+
export declare const getEnv: ({ variables, }: GetEnvInput) => Record<string, string>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=getEnv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEnv.d.ts","sourceRoot":"","sources":["../../../src/lib/env/getEnv.ts"],"names":[],"mappings":"AAIA,KAAK,WAAW,GAAG;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,GAAI,gBAEpB,WAAW,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CA2BrC,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { GenericException } from '../exception/genericException.js';
|
|
3
|
+
import { getLogger } from '../log/logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Get environment variables from the process.env object.
|
|
6
|
+
* Throws an exception if any of the variables are not defined
|
|
7
|
+
* and logs the missing variable names.
|
|
8
|
+
*
|
|
9
|
+
* @param variables - The list of environment variables to get
|
|
10
|
+
* @returns {Record<string, string>} Object of the environment variables
|
|
11
|
+
* @throws {GenericException} Thrown if any of the requested variables are not defined
|
|
12
|
+
*/
|
|
13
|
+
export const getEnv = ({ variables, }) => {
|
|
14
|
+
const envVars = {};
|
|
15
|
+
const missingEnvVars = [];
|
|
16
|
+
for (const variable of variables) {
|
|
17
|
+
if (!process.env[variable]) {
|
|
18
|
+
missingEnvVars.push(variable);
|
|
19
|
+
}
|
|
20
|
+
envVars[variable] = process.env[variable];
|
|
21
|
+
}
|
|
22
|
+
if (missingEnvVars.length > 0) {
|
|
23
|
+
getLogger().error({
|
|
24
|
+
category: 'Nimbus',
|
|
25
|
+
message: 'Undefined environment variables',
|
|
26
|
+
data: {
|
|
27
|
+
undefinedVariables: missingEnvVars,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
throw new GenericException('Undefined environment variables', {
|
|
31
|
+
undefinedVariables: missingEnvVars,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return envVars;
|
|
35
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventBus.d.ts","sourceRoot":"","sources":["../../../src/lib/eventBus/eventBus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"eventBus.d.ts","sourceRoot":"","sources":["../../../src/lib/eventBus/eventBus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AA2CjD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAC,MAAM,SAAS,KAAK,IAAI;IACpD;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD;;OAEG;IACH,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;CACjD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAyB;IAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;gBACS,OAAO,CAAC,EAAE,qBAAqB;IAU3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACI,QAAQ,CAAC,MAAM,SAAS,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAqD1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiDG;IACI,cAAc,CAAC,MAAM,SAAS,KAAK,EAAE,EACxC,IAAI,EACJ,OAAO,EACP,OAAO,EACP,OAAO,GACV,EAAE,mBAAmB,CAAC,MAAM,CAAC,GAAG,IAAI;IAqCrC,OAAO,CAAC,aAAa;IA4ErB,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,mBAAmB;IAwC3B;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;CAmB7B;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,aAAa,GACtB,MAAM,MAAM,EACZ,UAAU,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,KAC9C,IAEF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,WAAW,GAAI,OAAM,MAAkB,KAAG,cAStD,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { metrics, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api';
|
|
|
2
2
|
import EventEmitter from 'node:events';
|
|
3
3
|
import { GenericException } from '../exception/genericException.js';
|
|
4
4
|
import { getLogger } from '../log/logger.js';
|
|
5
|
+
import { withRetry } from '../retry/withRetry.js';
|
|
5
6
|
const tracer = trace.getTracer('nimbus');
|
|
6
7
|
const meter = metrics.getMeter('nimbus');
|
|
7
8
|
const eventsPublishedCounter = meter.createCounter('eventbus_events_published_total', {
|
|
@@ -301,35 +302,38 @@ export class NimbusEventBus {
|
|
|
301
302
|
}),
|
|
302
303
|
},
|
|
303
304
|
}, async (span) => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
try {
|
|
305
|
+
try {
|
|
306
|
+
await withRetry(async () => {
|
|
307
307
|
await handler(event);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
attempt
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
span,
|
|
319
|
-
metricLabels,
|
|
320
|
-
startTime,
|
|
321
|
-
retryConfig: {
|
|
322
|
-
maxRetries,
|
|
323
|
-
baseDelay,
|
|
324
|
-
maxDelay,
|
|
325
|
-
},
|
|
308
|
+
}, {
|
|
309
|
+
maxRetries,
|
|
310
|
+
initialDelayMs: baseDelay,
|
|
311
|
+
maxDelayMs: maxDelay,
|
|
312
|
+
jitterFactor: useJitter ? 0.1 : 0,
|
|
313
|
+
onRetry: ({ attempt, delayMs }) => {
|
|
314
|
+
retryAttemptsCounter.add(1, metricLabels);
|
|
315
|
+
span.addEvent('retry', {
|
|
316
|
+
attempt,
|
|
317
|
+
delay_ms: delayMs,
|
|
326
318
|
});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
this._recordDeliveryMetrics(metricLabels, 'success', startTime);
|
|
322
|
+
span.end();
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
this._handleFinalFailure({
|
|
326
|
+
error,
|
|
327
|
+
event,
|
|
328
|
+
span,
|
|
329
|
+
metricLabels,
|
|
330
|
+
startTime,
|
|
331
|
+
retryConfig: {
|
|
332
|
+
maxRetries,
|
|
333
|
+
baseDelay,
|
|
334
|
+
maxDelay,
|
|
335
|
+
},
|
|
336
|
+
});
|
|
333
337
|
}
|
|
334
338
|
});
|
|
335
339
|
}
|
|
@@ -355,11 +359,6 @@ export class NimbusEventBus {
|
|
|
355
359
|
}
|
|
356
360
|
throw exception;
|
|
357
361
|
}
|
|
358
|
-
_calculateRetryDelay(attempt, baseDelay, maxDelay, useJitter) {
|
|
359
|
-
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
|
360
|
-
const jitter = useJitter ? Math.random() * delay * 0.1 : 0;
|
|
361
|
-
return delay + jitter;
|
|
362
|
-
}
|
|
363
362
|
/**
|
|
364
363
|
* Validate the size of the event and return the size in bytes.
|
|
365
364
|
*
|
|
@@ -5,6 +5,6 @@ export declare class Exception extends Error {
|
|
|
5
5
|
details?: Record<string, unknown>;
|
|
6
6
|
statusCode?: number;
|
|
7
7
|
constructor(name: string, message: string, details?: Record<string, unknown>, statusCode?: number);
|
|
8
|
-
fromError(error: Error):
|
|
8
|
+
fromError(error: Error): this;
|
|
9
9
|
}
|
|
10
10
|
//# sourceMappingURL=exception.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exception.d.ts","sourceRoot":"","sources":["../../../src/lib/exception/exception.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAGvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,UAAU,CAAC,EAAE,MAAM;IAmBhB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,
|
|
1
|
+
{"version":3,"file":"exception.d.ts","sourceRoot":"","sources":["../../../src/lib/exception/exception.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAGvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,UAAU,CAAC,EAAE,MAAM;IAmBhB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;CAMvC"}
|
|
@@ -13,6 +13,6 @@ export declare class InvalidInputException extends Exception {
|
|
|
13
13
|
*
|
|
14
14
|
* @returns {InvalidInputException} The InvalidInputException.
|
|
15
15
|
*/
|
|
16
|
-
fromZodError(error: ZodError):
|
|
16
|
+
fromZodError(error: ZodError): this;
|
|
17
17
|
}
|
|
18
18
|
//# sourceMappingURL=invalidInputException.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invalidInputException.d.ts","sourceRoot":"","sources":["../../../src/lib/exception/invalidInputException.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;gBACpC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAS/D;;;;;;;OAOG;IACI,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"invalidInputException.d.ts","sourceRoot":"","sources":["../../../src/lib/exception/invalidInputException.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;gBACpC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAS/D;;;;;;;OAOG;IACI,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CAa7C"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { LogInput } from './logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Truncates a {@link LogInput} before it is turned into a log record.
|
|
4
|
+
*
|
|
5
|
+
* Use with {@link setupLogger} via the `truncator` option, or provide a custom
|
|
6
|
+
* implementation. The built-in factory is {@link createLogTruncator}.
|
|
7
|
+
*/
|
|
8
|
+
export type LogTruncator = (logInput: LogInput) => LogInput;
|
|
9
|
+
/**
|
|
10
|
+
* Options for {@link createLogTruncator}.
|
|
11
|
+
*
|
|
12
|
+
* All fields are optional; omitted fields use the built-in defaults.
|
|
13
|
+
*/
|
|
14
|
+
export type LogTruncatorOptions = {
|
|
15
|
+
/**
|
|
16
|
+
* Maximum serialized byte size of `data` after structural truncation.
|
|
17
|
+
* If still over budget (or not JSON-serializable), `data` is replaced
|
|
18
|
+
* with a size marker. Defaults to 16_384 (16 KB).
|
|
19
|
+
*/
|
|
20
|
+
maxBytes?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum number of array items kept inside `data` (also applies when
|
|
23
|
+
* converting `Map` / `Set` to arrays). Defaults to 50.
|
|
24
|
+
*/
|
|
25
|
+
maxArrayItems?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum number of own enumerable keys kept on plain objects inside
|
|
28
|
+
* `data`. Defaults to 50.
|
|
29
|
+
*/
|
|
30
|
+
maxObjectKeys?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Maximum object nesting depth inside `data`, and maximum depth when
|
|
33
|
+
* walking `error.cause` / aggregate error chains.
|
|
34
|
+
* Defaults to 8. Values below 1 are clamped to 1 so root `data` stays
|
|
35
|
+
* an object.
|
|
36
|
+
*/
|
|
37
|
+
maxDepth?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Maximum length of `category`.
|
|
40
|
+
* Defaults to 50.
|
|
41
|
+
*/
|
|
42
|
+
maxCategoryLength?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Maximum length of `message` and `error.message`.
|
|
45
|
+
* Defaults to 200.
|
|
46
|
+
*/
|
|
47
|
+
maxMessageLength?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Maximum length of `error.stack`.
|
|
50
|
+
* Defaults to 200.
|
|
51
|
+
*/
|
|
52
|
+
maxStackLength?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Maximum length of string values inside `data`.
|
|
55
|
+
* Defaults to 200.
|
|
56
|
+
*/
|
|
57
|
+
maxDataStringLength?: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Creates a {@link LogTruncator} that truncates each {@link LogInput} field
|
|
61
|
+
* separately (except `correlationId`, which is left untouched).
|
|
62
|
+
*
|
|
63
|
+
* - `message` / `category`: string length caps
|
|
64
|
+
* - `data`: structural truncation (arrays, objects, strings, depth, circular
|
|
65
|
+
* refs, common JS types) plus a byte-size cliff that replaces oversized or
|
|
66
|
+
* unserializable payloads with a size marker
|
|
67
|
+
* - `error`: stays `Error`-shaped; truncates `message` / `stack`, copies
|
|
68
|
+
* enumerable custom fields, and walks `cause` / aggregate errors up to
|
|
69
|
+
* `maxDepth`
|
|
70
|
+
*
|
|
71
|
+
* @param options - Optional limits; omitted fields use built-in defaults.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* import { createLogTruncator, setupLogger } from '@nimbus-cqrs/core';
|
|
76
|
+
*
|
|
77
|
+
* setupLogger({
|
|
78
|
+
* truncator: createLogTruncator(),
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* setupLogger({
|
|
82
|
+
* truncator: createLogTruncator({
|
|
83
|
+
* maxBytes: 8_192,
|
|
84
|
+
* maxArrayItems: 50,
|
|
85
|
+
* maxObjectKeys: 50,
|
|
86
|
+
* maxDepth: 8,
|
|
87
|
+
* maxCategoryLength: 50,
|
|
88
|
+
* maxMessageLength: 100,
|
|
89
|
+
* maxStackLength: 200,
|
|
90
|
+
* maxDataStringLength: 200,
|
|
91
|
+
* }),
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare const createLogTruncator: (options?: LogTruncatorOptions) => LogTruncator;
|
|
96
|
+
//# sourceMappingURL=logTruncator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logTruncator.d.ts","sourceRoot":"","sources":["../../../src/lib/log/logTruncator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAsDF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,kBAAkB,GAC3B,UAAS,mBAAwB,KAClC,YA+TF,CAAC"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
const DEFAULT_MAX_BYTES = 16_384;
|
|
2
|
+
const DEFAULT_MAX_ARRAY_ITEMS = 50;
|
|
3
|
+
const DEFAULT_MAX_OBJECT_KEYS = 50;
|
|
4
|
+
const DEFAULT_MAX_DEPTH = 8;
|
|
5
|
+
const DEFAULT_MAX_CATEGORY_LENGTH = 50;
|
|
6
|
+
const DEFAULT_MAX_MESSAGE_LENGTH = 200;
|
|
7
|
+
const DEFAULT_MAX_STACK_LENGTH = 200;
|
|
8
|
+
const DEFAULT_MAX_DATA_STRING_LENGTH = 200;
|
|
9
|
+
const serializedByteSize = (value) => {
|
|
10
|
+
try {
|
|
11
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const TRUNCATED_STRING_SUFFIX = ' [... __truncated]';
|
|
18
|
+
const truncateString = (value, maxLength) => {
|
|
19
|
+
if (value.length <= maxLength) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
return `${value.slice(0, maxLength)}${TRUNCATED_STRING_SUFFIX}`;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Creates a {@link LogTruncator} that truncates each {@link LogInput} field
|
|
26
|
+
* separately (except `correlationId`, which is left untouched).
|
|
27
|
+
*
|
|
28
|
+
* - `message` / `category`: string length caps
|
|
29
|
+
* - `data`: structural truncation (arrays, objects, strings, depth, circular
|
|
30
|
+
* refs, common JS types) plus a byte-size cliff that replaces oversized or
|
|
31
|
+
* unserializable payloads with a size marker
|
|
32
|
+
* - `error`: stays `Error`-shaped; truncates `message` / `stack`, copies
|
|
33
|
+
* enumerable custom fields, and walks `cause` / aggregate errors up to
|
|
34
|
+
* `maxDepth`
|
|
35
|
+
*
|
|
36
|
+
* @param options - Optional limits; omitted fields use built-in defaults.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { createLogTruncator, setupLogger } from '@nimbus-cqrs/core';
|
|
41
|
+
*
|
|
42
|
+
* setupLogger({
|
|
43
|
+
* truncator: createLogTruncator(),
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* setupLogger({
|
|
47
|
+
* truncator: createLogTruncator({
|
|
48
|
+
* maxBytes: 8_192,
|
|
49
|
+
* maxArrayItems: 50,
|
|
50
|
+
* maxObjectKeys: 50,
|
|
51
|
+
* maxDepth: 8,
|
|
52
|
+
* maxCategoryLength: 50,
|
|
53
|
+
* maxMessageLength: 100,
|
|
54
|
+
* maxStackLength: 200,
|
|
55
|
+
* maxDataStringLength: 200,
|
|
56
|
+
* }),
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const createLogTruncator = (options = {}) => {
|
|
61
|
+
// Clamp so root `data` stays an object and string/array limits stay usable.
|
|
62
|
+
const maxBytes = Math.max(0, options.maxBytes ?? DEFAULT_MAX_BYTES);
|
|
63
|
+
const maxArrayItems = Math.max(0, options.maxArrayItems ?? DEFAULT_MAX_ARRAY_ITEMS);
|
|
64
|
+
const maxObjectKeys = Math.max(0, options.maxObjectKeys ?? DEFAULT_MAX_OBJECT_KEYS);
|
|
65
|
+
const maxDepth = Math.max(1, options.maxDepth ?? DEFAULT_MAX_DEPTH);
|
|
66
|
+
const maxCategoryLength = Math.max(0, options.maxCategoryLength ?? DEFAULT_MAX_CATEGORY_LENGTH);
|
|
67
|
+
const maxMessageLength = Math.max(0, options.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH);
|
|
68
|
+
const maxStackLength = Math.max(0, options.maxStackLength ?? DEFAULT_MAX_STACK_LENGTH);
|
|
69
|
+
const maxDataStringLength = Math.max(0, options.maxDataStringLength ?? DEFAULT_MAX_DATA_STRING_LENGTH);
|
|
70
|
+
const truncateDataValue = (value, depth, seen) => {
|
|
71
|
+
if (typeof value === 'bigint') {
|
|
72
|
+
return truncateString(`${value}n`, maxDataStringLength);
|
|
73
|
+
}
|
|
74
|
+
if (value === null || typeof value !== 'object') {
|
|
75
|
+
if (typeof value === 'string' &&
|
|
76
|
+
value.length > maxDataStringLength) {
|
|
77
|
+
return truncateString(value, maxDataStringLength);
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
if (seen.has(value)) {
|
|
82
|
+
return '[Circular]';
|
|
83
|
+
}
|
|
84
|
+
if (depth >= maxDepth) {
|
|
85
|
+
return '[Max depth]';
|
|
86
|
+
}
|
|
87
|
+
seen.add(value);
|
|
88
|
+
try {
|
|
89
|
+
if (value instanceof Date) {
|
|
90
|
+
return truncateString(value.toISOString(), maxDataStringLength);
|
|
91
|
+
}
|
|
92
|
+
if (value instanceof RegExp) {
|
|
93
|
+
return truncateString(value.toString(), maxDataStringLength);
|
|
94
|
+
}
|
|
95
|
+
if (value instanceof ArrayBuffer) {
|
|
96
|
+
return {
|
|
97
|
+
__truncated: true,
|
|
98
|
+
reason: 'arrayBuffer',
|
|
99
|
+
byteLength: value.byteLength,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (ArrayBuffer.isView(value)) {
|
|
103
|
+
return {
|
|
104
|
+
__truncated: true,
|
|
105
|
+
reason: 'typedArray',
|
|
106
|
+
type: value.constructor.name,
|
|
107
|
+
byteLength: value.byteLength,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (value instanceof Error) {
|
|
111
|
+
return truncateErrorAsData(value, depth, seen);
|
|
112
|
+
}
|
|
113
|
+
if (value instanceof Map) {
|
|
114
|
+
return truncateDataValue(Array.from(value.entries()), depth + 1, seen);
|
|
115
|
+
}
|
|
116
|
+
if (value instanceof Set) {
|
|
117
|
+
return truncateDataValue(Array.from(value.values()), depth + 1, seen);
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(value)) {
|
|
120
|
+
if (value.length > maxArrayItems) {
|
|
121
|
+
return [
|
|
122
|
+
...value
|
|
123
|
+
.slice(0, maxArrayItems)
|
|
124
|
+
.map((item) => truncateDataValue(item, depth + 1, seen)),
|
|
125
|
+
{
|
|
126
|
+
__truncated: true,
|
|
127
|
+
omittedCount: value.length - maxArrayItems,
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
return value.map((item) => truncateDataValue(item, depth + 1, seen));
|
|
132
|
+
}
|
|
133
|
+
const entries = Object.entries(value);
|
|
134
|
+
const limited = entries.length > maxObjectKeys
|
|
135
|
+
? entries.slice(0, maxObjectKeys)
|
|
136
|
+
: entries;
|
|
137
|
+
const result = {};
|
|
138
|
+
for (const [key, entry] of limited) {
|
|
139
|
+
result[key] = truncateDataValue(entry, depth + 1, seen);
|
|
140
|
+
}
|
|
141
|
+
if (entries.length > maxObjectKeys) {
|
|
142
|
+
result.__truncated = true;
|
|
143
|
+
result.omittedKeyCount = entries.length - maxObjectKeys;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
// Recursion stack: allow shared refs, still detect true cycles.
|
|
149
|
+
seen.delete(value);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const truncateErrorAsData = (error, depth, seen) => {
|
|
153
|
+
const result = {
|
|
154
|
+
name: error.name,
|
|
155
|
+
message: truncateString(error.message, maxMessageLength),
|
|
156
|
+
};
|
|
157
|
+
if (typeof error.stack === 'string') {
|
|
158
|
+
result.stack = truncateString(error.stack, maxStackLength);
|
|
159
|
+
}
|
|
160
|
+
for (const [key, entry] of Object.entries(error)) {
|
|
161
|
+
if (key === 'name' ||
|
|
162
|
+
key === 'message' ||
|
|
163
|
+
key === 'stack' ||
|
|
164
|
+
key === 'cause' ||
|
|
165
|
+
key === 'errors') {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
result[key] = truncateDataValue(entry, depth + 1, seen);
|
|
169
|
+
}
|
|
170
|
+
if ('cause' in error && error.cause !== undefined) {
|
|
171
|
+
result.cause = truncateDataValue(error.cause, depth + 1, seen);
|
|
172
|
+
}
|
|
173
|
+
if (typeof AggregateError !== 'undefined' &&
|
|
174
|
+
error instanceof AggregateError) {
|
|
175
|
+
result.errors = truncateDataValue(error.errors, depth + 1, seen);
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
};
|
|
179
|
+
const truncateData = (data) => {
|
|
180
|
+
const truncated = truncateDataValue(data, 0, new WeakSet());
|
|
181
|
+
const byteSize = serializedByteSize(truncated);
|
|
182
|
+
if (byteSize !== undefined && byteSize <= maxBytes) {
|
|
183
|
+
return truncated;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
__truncated: true,
|
|
187
|
+
reason: byteSize === undefined ? 'unserializable' : 'size',
|
|
188
|
+
...(byteSize !== undefined ? { serializedByteSize: byteSize } : {}),
|
|
189
|
+
maxBytes,
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
const copyCustomErrorFields = (source, target, depth) => {
|
|
193
|
+
for (const [key, entry] of Object.entries(source)) {
|
|
194
|
+
if (key === 'name' ||
|
|
195
|
+
key === 'message' ||
|
|
196
|
+
key === 'stack' ||
|
|
197
|
+
key === 'cause' ||
|
|
198
|
+
key === 'errors') {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
target[key] =
|
|
202
|
+
entry instanceof Error
|
|
203
|
+
? truncateError(entry, depth + 1)
|
|
204
|
+
: truncateDataValue(entry, depth + 1, new WeakSet());
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const truncateError = (error, depth) => {
|
|
208
|
+
if (depth >= maxDepth) {
|
|
209
|
+
const maxDepthError = new Error('[Max depth]');
|
|
210
|
+
maxDepthError.name = error.name;
|
|
211
|
+
return maxDepthError;
|
|
212
|
+
}
|
|
213
|
+
const message = truncateString(error.message, maxMessageLength);
|
|
214
|
+
let truncated;
|
|
215
|
+
if (typeof AggregateError !== 'undefined' &&
|
|
216
|
+
error instanceof AggregateError) {
|
|
217
|
+
truncated = new AggregateError(error.errors.map((item) => item instanceof Error
|
|
218
|
+
? truncateError(item, depth + 1)
|
|
219
|
+
: truncateDataValue(item, depth + 1, new WeakSet())), message);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
truncated = new Error(message);
|
|
223
|
+
}
|
|
224
|
+
truncated.name = error.name;
|
|
225
|
+
if (typeof error.stack === 'string') {
|
|
226
|
+
truncated.stack = truncateString(error.stack, maxStackLength);
|
|
227
|
+
}
|
|
228
|
+
if ('cause' in error && error.cause !== undefined) {
|
|
229
|
+
truncated.cause = error.cause instanceof Error
|
|
230
|
+
? truncateError(error.cause, depth + 1)
|
|
231
|
+
: truncateDataValue(error.cause, depth + 1, new WeakSet());
|
|
232
|
+
}
|
|
233
|
+
copyCustomErrorFields(error, truncated, depth);
|
|
234
|
+
return truncated;
|
|
235
|
+
};
|
|
236
|
+
return (logInput) => {
|
|
237
|
+
const truncated = {
|
|
238
|
+
message: truncateString(logInput.message, maxMessageLength),
|
|
239
|
+
};
|
|
240
|
+
if (logInput.category !== undefined) {
|
|
241
|
+
truncated.category = truncateString(logInput.category, maxCategoryLength);
|
|
242
|
+
}
|
|
243
|
+
if (logInput.data !== undefined) {
|
|
244
|
+
truncated.data = truncateData(logInput.data);
|
|
245
|
+
}
|
|
246
|
+
if (logInput.error !== undefined) {
|
|
247
|
+
truncated.error = truncateError(logInput.error, 0);
|
|
248
|
+
}
|
|
249
|
+
if (logInput.correlationId !== undefined) {
|
|
250
|
+
truncated.correlationId = logInput.correlationId;
|
|
251
|
+
}
|
|
252
|
+
return truncated;
|
|
253
|
+
};
|
|
254
|
+
};
|