zario 0.3.5 → 0.4.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/README.md +102 -359
- package/dist/cjs/aggregation/LogAggregator.js +60 -31
- package/dist/cjs/core/Formatter.js +3 -4
- package/dist/cjs/core/Logger.js +41 -24
- package/dist/cjs/filters/Filter.js +52 -18
- package/dist/cjs/filters/index.js +0 -1
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/transports/CircuitBreakerTransport.js +171 -0
- package/dist/cjs/transports/DeadLetterQueue.js +129 -0
- package/dist/cjs/transports/FileTransport.js +78 -7
- package/dist/cjs/transports/HttpTransport.js +15 -3
- package/dist/cjs/transports/RetryTransport.js +199 -0
- package/dist/cjs/transports/index.js +3 -0
- package/dist/cjs/types/TypeInterfaces.js +2 -0
- package/dist/cjs/utils/index.js +78 -0
- package/dist/esm/aggregation/LogAggregator.d.ts +8 -29
- package/dist/esm/aggregation/LogAggregator.js +60 -31
- package/dist/esm/core/Formatter.js +1 -2
- package/dist/esm/core/Logger.d.ts +13 -3
- package/dist/esm/core/Logger.js +40 -23
- package/dist/esm/filters/Filter.d.ts +24 -22
- package/dist/esm/filters/Filter.js +47 -17
- package/dist/esm/filters/index.d.ts +0 -1
- package/dist/esm/filters/index.js +0 -1
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +6 -3
- package/dist/esm/transports/CircuitBreakerTransport.d.ts +43 -0
- package/dist/esm/transports/CircuitBreakerTransport.js +167 -0
- package/dist/esm/transports/DeadLetterQueue.d.ts +34 -0
- package/dist/esm/transports/DeadLetterQueue.js +92 -0
- package/dist/esm/transports/FileTransport.d.ts +4 -0
- package/dist/esm/transports/FileTransport.js +78 -7
- package/dist/esm/transports/HttpTransport.d.ts +2 -0
- package/dist/esm/transports/HttpTransport.js +15 -3
- package/dist/esm/transports/RetryTransport.d.ts +67 -0
- package/dist/esm/transports/RetryTransport.js +195 -0
- package/dist/esm/transports/Transport.d.ts +1 -0
- package/dist/esm/transports/index.d.ts +3 -0
- package/dist/esm/transports/index.js +3 -0
- package/dist/esm/types/TypeInterfaces.d.ts +7 -0
- package/dist/esm/types/TypeInterfaces.js +1 -0
- package/dist/esm/utils/index.d.ts +15 -0
- package/dist/esm/utils/index.js +72 -0
- package/package.json +87 -71
- package/dist/cjs/filters/SpecificFilters.js +0 -71
- package/dist/cjs/utils/ColorUtil.js +0 -42
- package/dist/cjs/utils/TimeUtil.js +0 -26
- package/dist/cjs/utils/Timerutil.js +0 -22
- package/dist/esm/filters/SpecificFilters.d.ts +0 -41
- package/dist/esm/filters/SpecificFilters.js +0 -64
- package/dist/esm/utils/ColorUtil.d.ts +0 -4
- package/dist/esm/utils/ColorUtil.js +0 -38
- package/dist/esm/utils/TimeUtil.d.ts +0 -3
- package/dist/esm/utils/TimeUtil.js +0 -22
- package/dist/esm/utils/Timerutil.d.ts +0 -8
- package/dist/esm/utils/Timerutil.js +0 -18
package/dist/cjs/core/Logger.js
CHANGED
|
@@ -3,25 +3,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Logger = void 0;
|
|
4
4
|
const Formatter_js_1 = require("./Formatter.js");
|
|
5
5
|
const ConsoleTransport_js_1 = require("../transports/ConsoleTransport.js");
|
|
6
|
+
const RetryTransport_js_1 = require("../transports/RetryTransport.js");
|
|
6
7
|
const StructuredExtensions_js_1 = require("../structured/StructuredExtensions.js");
|
|
7
|
-
|
|
8
|
+
const index_js_1 = require("../utils/index.js");
|
|
9
|
+
const events_1 = require("events");
|
|
10
|
+
class Logger extends events_1.EventEmitter {
|
|
8
11
|
constructor(options = {}) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
this.
|
|
12
|
-
const { level, colorize, json, transports = [], timestampFormat = "YYYY-MM-DD HH:mm:ss", prefix, timestamp, context = {}, parent, asyncMode, customLevels = {}, customColors = {}, filters = [], aggregators = [], enrichers, } = options;
|
|
13
|
-
this.parent = parent; // Set parent
|
|
12
|
+
const { level, colorize, json, transports = [], timestampFormat = "YYYY-MM-DD HH:mm:ss", prefix, timestamp, context = {}, parent, asyncMode, async, customLevels = {}, customColors = {}, filters = [], aggregators = [], enrichers, } = options;
|
|
13
|
+
super();
|
|
14
|
+
this.parent = parent;
|
|
14
15
|
this.context = { ...context }; // Init context
|
|
15
16
|
this.customLevels = customLevels; // custom log store
|
|
16
17
|
this.asyncMode = false;
|
|
17
18
|
this.filters = [...filters]; // Copy filters
|
|
18
19
|
this.aggregators = [...aggregators]; // Copy aggregators
|
|
19
|
-
this.enrichers = enrichers ?? new StructuredExtensions_js_1.LogEnrichmentPipeline();
|
|
20
|
+
this.enrichers = enrichers ?? new StructuredExtensions_js_1.LogEnrichmentPipeline();
|
|
21
|
+
this.deadLetterQueue = options.deadLetterQueue;
|
|
22
|
+
this.retryOptions = options.retryOptions;
|
|
20
23
|
if (this.parent) {
|
|
21
24
|
this.level = level ?? this.parent.level;
|
|
22
25
|
this.prefix = prefix ?? this.parent.prefix;
|
|
23
26
|
this.timestamp = timestamp ?? this.parent.timestamp;
|
|
24
|
-
this.asyncMode = asyncMode ?? this.parent.asyncMode;
|
|
27
|
+
this.asyncMode = (async ?? asyncMode) ?? this.parent.asyncMode;
|
|
25
28
|
this.transports =
|
|
26
29
|
transports && transports.length > 0
|
|
27
30
|
? this.initTransports(transports)
|
|
@@ -67,7 +70,7 @@ class Logger {
|
|
|
67
70
|
const defaultTransports = transports && transports.length > 0
|
|
68
71
|
? transports
|
|
69
72
|
: this.getDefaultTransports(isProd);
|
|
70
|
-
this.asyncMode = asyncMode ?? this.getDefaultAsyncMode(isProd);
|
|
73
|
+
this.asyncMode = (async ?? asyncMode) ?? this.getDefaultAsyncMode(isProd);
|
|
71
74
|
this.transports = this.initTransports(defaultTransports);
|
|
72
75
|
this.formatter = new Formatter_js_1.Formatter({
|
|
73
76
|
colorize: this.getDefaultColorizeValue(colorize),
|
|
@@ -114,7 +117,15 @@ class Logger {
|
|
|
114
117
|
const initializedTransports = [];
|
|
115
118
|
for (const transportConfig of transportConfigs) {
|
|
116
119
|
if (this.isTransport(transportConfig)) {
|
|
117
|
-
|
|
120
|
+
let transport = transportConfig;
|
|
121
|
+
// Check if transport is already a RetryTransport to avoid double-wrapping
|
|
122
|
+
if (this.retryOptions && !(transport instanceof RetryTransport_js_1.RetryTransport)) {
|
|
123
|
+
transport = new RetryTransport_js_1.RetryTransport({
|
|
124
|
+
...this.retryOptions,
|
|
125
|
+
wrappedTransport: transport
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
initializedTransports.push(transport);
|
|
118
129
|
}
|
|
119
130
|
}
|
|
120
131
|
return initializedTransports;
|
|
@@ -171,7 +182,14 @@ class Logger {
|
|
|
171
182
|
prefix: this.prefix,
|
|
172
183
|
};
|
|
173
184
|
// Apply enrichers to the log data
|
|
174
|
-
|
|
185
|
+
try {
|
|
186
|
+
logData = this.enrichers.process(logData);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
console.error('Error in enrichers:', error);
|
|
190
|
+
this.emit('error', { type: 'enricher', error });
|
|
191
|
+
// Continue with original logData if enrichment fails
|
|
192
|
+
}
|
|
175
193
|
// Check if the log should be emitted based on filters
|
|
176
194
|
// Use a copy to prevent concurrent modification issues if filters are modified during logging
|
|
177
195
|
const currentFilters = [...this.filters];
|
|
@@ -186,6 +204,7 @@ class Logger {
|
|
|
186
204
|
if (transport.writeAsync) {
|
|
187
205
|
transport.writeAsync(logData, this.formatter).catch((error) => {
|
|
188
206
|
console.error("Error during async logging:", error);
|
|
207
|
+
this.emit('error', { type: 'transport', error });
|
|
189
208
|
});
|
|
190
209
|
}
|
|
191
210
|
else {
|
|
@@ -208,6 +227,7 @@ class Logger {
|
|
|
208
227
|
}
|
|
209
228
|
catch (error) {
|
|
210
229
|
console.error('Error in aggregator:', error);
|
|
230
|
+
this.emit('error', { type: 'aggregator', error });
|
|
211
231
|
}
|
|
212
232
|
}
|
|
213
233
|
}
|
|
@@ -224,6 +244,9 @@ class Logger {
|
|
|
224
244
|
error(message, metadata) {
|
|
225
245
|
this.log("error", message, metadata);
|
|
226
246
|
}
|
|
247
|
+
fatal(message, metadata) {
|
|
248
|
+
this.log("fatal", message, metadata);
|
|
249
|
+
}
|
|
227
250
|
silent(message, metadata) {
|
|
228
251
|
this.log("silent", message, metadata);
|
|
229
252
|
}
|
|
@@ -261,8 +284,7 @@ class Logger {
|
|
|
261
284
|
return new Logger({ ...options, parent: this });
|
|
262
285
|
}
|
|
263
286
|
startTimer(name) {
|
|
264
|
-
|
|
265
|
-
return new Timer(name, (message) => this.info(message));
|
|
287
|
+
return new index_js_1.Timer(name, (message) => this.info(message));
|
|
266
288
|
}
|
|
267
289
|
/**
|
|
268
290
|
* Add a filter to the logger
|
|
@@ -310,21 +332,16 @@ class Logger {
|
|
|
310
332
|
async flushAggregators() {
|
|
311
333
|
const flushPromises = [];
|
|
312
334
|
for (const aggregator of this.aggregators) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// Wrap the promise to catch and log rejections, so one failure doesn't stop others
|
|
317
|
-
flushPromises.push(result.catch(error => {
|
|
318
|
-
console.error('Error flushing aggregator:', error);
|
|
319
|
-
}));
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
catch (error) {
|
|
323
|
-
console.error('Error flushing aggregator:', error);
|
|
335
|
+
const result = aggregator.flush();
|
|
336
|
+
if (result instanceof Promise) {
|
|
337
|
+
flushPromises.push(result);
|
|
324
338
|
}
|
|
325
339
|
}
|
|
326
340
|
await Promise.all(flushPromises);
|
|
327
341
|
}
|
|
342
|
+
getTransports() {
|
|
343
|
+
return this.transports;
|
|
344
|
+
}
|
|
328
345
|
}
|
|
329
346
|
exports.Logger = Logger;
|
|
330
347
|
Logger.defaultTransportsFactory = null;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PredicateFilter = exports.NotFilter = exports.OrFilter = exports.CompositeFilter = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* A filter that combines multiple filters with AND logic
|
|
6
|
-
* Note: With an empty array of filters, this returns true (allows all logs).
|
|
7
|
-
* This follows the mathematical concept of vacuous truth - "all" conditions
|
|
8
|
-
* are satisfied when there are no conditions to check.
|
|
9
|
-
*/
|
|
3
|
+
exports.FieldFilter = exports.MetadataFilter = exports.PrefixFilter = exports.LevelFilter = exports.PredicateFilter = exports.NotFilter = exports.OrFilter = exports.CompositeFilter = void 0;
|
|
10
4
|
class CompositeFilter {
|
|
11
5
|
constructor(filters) {
|
|
12
6
|
this.filters = filters;
|
|
@@ -16,11 +10,6 @@ class CompositeFilter {
|
|
|
16
10
|
}
|
|
17
11
|
}
|
|
18
12
|
exports.CompositeFilter = CompositeFilter;
|
|
19
|
-
/**
|
|
20
|
-
* A filter that combines multiple filters with OR logic
|
|
21
|
-
* Note: With an empty array of filters, this returns false (blocks all logs).
|
|
22
|
-
* This is because there are no matching conditions when the filter array is empty.
|
|
23
|
-
*/
|
|
24
13
|
class OrFilter {
|
|
25
14
|
constructor(filters) {
|
|
26
15
|
this.filters = filters;
|
|
@@ -30,9 +19,6 @@ class OrFilter {
|
|
|
30
19
|
}
|
|
31
20
|
}
|
|
32
21
|
exports.OrFilter = OrFilter;
|
|
33
|
-
/**
|
|
34
|
-
* A filter that negates another filter
|
|
35
|
-
*/
|
|
36
22
|
class NotFilter {
|
|
37
23
|
constructor(filter) {
|
|
38
24
|
this.filter = filter;
|
|
@@ -42,9 +28,6 @@ class NotFilter {
|
|
|
42
28
|
}
|
|
43
29
|
}
|
|
44
30
|
exports.NotFilter = NotFilter;
|
|
45
|
-
/**
|
|
46
|
-
* A filter based on a predicate function
|
|
47
|
-
*/
|
|
48
31
|
class PredicateFilter {
|
|
49
32
|
constructor(predicate) {
|
|
50
33
|
this.predicate = predicate;
|
|
@@ -54,3 +37,54 @@ class PredicateFilter {
|
|
|
54
37
|
}
|
|
55
38
|
}
|
|
56
39
|
exports.PredicateFilter = PredicateFilter;
|
|
40
|
+
class LevelFilter {
|
|
41
|
+
constructor(allowedLevels) {
|
|
42
|
+
this.allowedLevels = new Set(allowedLevels);
|
|
43
|
+
}
|
|
44
|
+
shouldEmit(logData) {
|
|
45
|
+
return this.allowedLevels.has(logData.level);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.LevelFilter = LevelFilter;
|
|
49
|
+
class PrefixFilter {
|
|
50
|
+
constructor(allowedPrefixes) {
|
|
51
|
+
this.allowedPrefixes = new Set(allowedPrefixes);
|
|
52
|
+
}
|
|
53
|
+
shouldEmit(logData) {
|
|
54
|
+
if (!logData.prefix) {
|
|
55
|
+
return this.allowedPrefixes.has('');
|
|
56
|
+
}
|
|
57
|
+
return this.allowedPrefixes.has(logData.prefix);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.PrefixFilter = PrefixFilter;
|
|
61
|
+
class MetadataFilter {
|
|
62
|
+
constructor(requiredMetadata) {
|
|
63
|
+
this.requiredMetadata = requiredMetadata;
|
|
64
|
+
}
|
|
65
|
+
shouldEmit(logData) {
|
|
66
|
+
if (!logData.metadata) {
|
|
67
|
+
return Object.keys(this.requiredMetadata).length === 0;
|
|
68
|
+
}
|
|
69
|
+
for (const [key, value] of Object.entries(this.requiredMetadata)) {
|
|
70
|
+
if (logData.metadata[key] !== value) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.MetadataFilter = MetadataFilter;
|
|
78
|
+
class FieldFilter {
|
|
79
|
+
constructor(fieldName, expectedValue) {
|
|
80
|
+
this.fieldName = fieldName;
|
|
81
|
+
this.expectedValue = expectedValue;
|
|
82
|
+
}
|
|
83
|
+
shouldEmit(logData) {
|
|
84
|
+
if (!logData.metadata) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return logData.metadata[this.fieldName] === this.expectedValue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.FieldFilter = FieldFilter;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LogEnrichmentPipeline = exports.MetadataEnricher = exports.CompositeAggregator = exports.TimeBasedAggregator = exports.BatchAggregator = exports.FieldFilter = exports.MetadataFilter = exports.PrefixFilter = exports.LevelFilter = exports.PredicateFilter = exports.NotFilter = exports.OrFilter = exports.CompositeFilter = exports.FilterableTransport = exports.HttpTransport = exports.FileTransport = exports.ConsoleTransport = exports.Logger = void 0;
|
|
3
|
+
exports.Timer = exports.LogEnrichmentPipeline = exports.MetadataEnricher = exports.CompositeAggregator = exports.TimeBasedAggregator = exports.BatchAggregator = exports.FieldFilter = exports.MetadataFilter = exports.PrefixFilter = exports.LevelFilter = exports.PredicateFilter = exports.NotFilter = exports.OrFilter = exports.CompositeFilter = exports.DeadLetterQueue = exports.CircuitBreakerTransport = exports.RetryTransport = exports.FilterableTransport = exports.HttpTransport = exports.FileTransport = exports.ConsoleTransport = exports.Logger = void 0;
|
|
4
4
|
const Logger_js_1 = require("./core/Logger.js");
|
|
5
5
|
Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return Logger_js_1.Logger; } });
|
|
6
6
|
const index_js_1 = require("./transports/index.js");
|
|
@@ -8,6 +8,9 @@ Object.defineProperty(exports, "ConsoleTransport", { enumerable: true, get: func
|
|
|
8
8
|
Object.defineProperty(exports, "FileTransport", { enumerable: true, get: function () { return index_js_1.FileTransport; } });
|
|
9
9
|
Object.defineProperty(exports, "HttpTransport", { enumerable: true, get: function () { return index_js_1.HttpTransport; } });
|
|
10
10
|
Object.defineProperty(exports, "FilterableTransport", { enumerable: true, get: function () { return index_js_1.FilterableTransport; } });
|
|
11
|
+
Object.defineProperty(exports, "RetryTransport", { enumerable: true, get: function () { return index_js_1.RetryTransport; } });
|
|
12
|
+
Object.defineProperty(exports, "CircuitBreakerTransport", { enumerable: true, get: function () { return index_js_1.CircuitBreakerTransport; } });
|
|
13
|
+
Object.defineProperty(exports, "DeadLetterQueue", { enumerable: true, get: function () { return index_js_1.DeadLetterQueue; } });
|
|
11
14
|
const index_js_2 = require("./filters/index.js");
|
|
12
15
|
Object.defineProperty(exports, "CompositeFilter", { enumerable: true, get: function () { return index_js_2.CompositeFilter; } });
|
|
13
16
|
Object.defineProperty(exports, "OrFilter", { enumerable: true, get: function () { return index_js_2.OrFilter; } });
|
|
@@ -24,6 +27,8 @@ Object.defineProperty(exports, "CompositeAggregator", { enumerable: true, get: f
|
|
|
24
27
|
const index_js_4 = require("./structured/index.js");
|
|
25
28
|
Object.defineProperty(exports, "MetadataEnricher", { enumerable: true, get: function () { return index_js_4.MetadataEnricher; } });
|
|
26
29
|
Object.defineProperty(exports, "LogEnrichmentPipeline", { enumerable: true, get: function () { return index_js_4.LogEnrichmentPipeline; } });
|
|
30
|
+
const index_js_5 = require("./utils/index.js");
|
|
31
|
+
Object.defineProperty(exports, "Timer", { enumerable: true, get: function () { return index_js_5.Timer; } });
|
|
27
32
|
// Configure default transports to maintain backward compatibility
|
|
28
33
|
Logger_js_1.Logger.defaultTransportsFactory = (isProd) => {
|
|
29
34
|
if (isProd) {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CircuitBreakerTransport = void 0;
|
|
4
|
+
class CircuitBreakerTransport {
|
|
5
|
+
constructor(baseTransport, options = {}) {
|
|
6
|
+
this.baseTransport = this.createTransport(baseTransport);
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.state = {
|
|
9
|
+
failureCount: 0,
|
|
10
|
+
lastFailureTime: 0,
|
|
11
|
+
state: 'open'
|
|
12
|
+
};
|
|
13
|
+
this.metrics = {
|
|
14
|
+
totalRequests: 0,
|
|
15
|
+
failedRequests: 0,
|
|
16
|
+
successfulRequests: 0,
|
|
17
|
+
currentState: 'open',
|
|
18
|
+
averageResponseTime: 0
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
createTransport(config) {
|
|
22
|
+
if (typeof config === 'function') {
|
|
23
|
+
const TransportClass = config;
|
|
24
|
+
return new TransportClass();
|
|
25
|
+
}
|
|
26
|
+
else if (typeof config === 'object' && config !== null) {
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
throw new Error('Invalid transport configuration');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
write(data, formatter) {
|
|
34
|
+
if (!this.canWrite()) {
|
|
35
|
+
throw new Error('Circuit breaker is open');
|
|
36
|
+
}
|
|
37
|
+
this.metrics.totalRequests++;
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
try {
|
|
40
|
+
this.baseTransport.write(data, formatter);
|
|
41
|
+
this.metrics.successfulRequests++;
|
|
42
|
+
this.resetFailureCount();
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
this.metrics.failedRequests++;
|
|
46
|
+
this.recordFailure();
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
const duration = Date.now() - startTime;
|
|
50
|
+
this.updateAverageResponseTime(duration);
|
|
51
|
+
}
|
|
52
|
+
async writeAsync(data, formatter) {
|
|
53
|
+
if (!this.canWrite()) {
|
|
54
|
+
throw new Error('Circuit breaker is open');
|
|
55
|
+
}
|
|
56
|
+
this.metrics.totalRequests++;
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
try {
|
|
59
|
+
if (this.baseTransport.writeAsync) {
|
|
60
|
+
await this.baseTransport.writeAsync(data, formatter);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.baseTransport.write(data, formatter);
|
|
64
|
+
}
|
|
65
|
+
this.metrics.successfulRequests++;
|
|
66
|
+
this.resetFailureCount();
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
this.metrics.failedRequests++;
|
|
70
|
+
this.recordFailure();
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
const duration = Date.now() - startTime;
|
|
74
|
+
this.updateAverageResponseTime(duration);
|
|
75
|
+
}
|
|
76
|
+
canWrite() {
|
|
77
|
+
const threshold = this.options.threshold || 5;
|
|
78
|
+
const timeout = this.options.timeout || 60000;
|
|
79
|
+
if (this.state.state === 'closed') {
|
|
80
|
+
const timeSinceOpen = Date.now() - this.state.lastFailureTime;
|
|
81
|
+
if (timeSinceOpen >= timeout) {
|
|
82
|
+
this.setState('open');
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (this.state.failureCount >= threshold) {
|
|
88
|
+
this.setState('closed');
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (this.state.state === 'half-open' && this.state.lastFailureTime > 0 && Date.now() - this.state.lastFailureTime < timeout) {
|
|
92
|
+
this.setState('open');
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return this.state.state === 'open' || this.state.state === 'half-open';
|
|
96
|
+
}
|
|
97
|
+
setState(newState) {
|
|
98
|
+
const oldState = this.state.state;
|
|
99
|
+
if (oldState !== newState) {
|
|
100
|
+
this.state.state = newState;
|
|
101
|
+
this.options.onStateChange?.(oldState, newState);
|
|
102
|
+
if (newState === 'closed') {
|
|
103
|
+
this.options.onTrip?.(this.state.failureCount);
|
|
104
|
+
}
|
|
105
|
+
else if (newState === 'open' && oldState === 'closed') {
|
|
106
|
+
this.options.onReset?.();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
resetFailureCount() {
|
|
111
|
+
if (this.state.failureCount === 0)
|
|
112
|
+
return;
|
|
113
|
+
this.state.failureCount = Math.max(0, Math.floor(this.state.failureCount * 0.9));
|
|
114
|
+
}
|
|
115
|
+
updateAverageResponseTime(duration) {
|
|
116
|
+
if (this.metrics.totalRequests > 0) {
|
|
117
|
+
// Use exponential moving average: newAvg = oldAvg * 0.9 + duration * 0.1
|
|
118
|
+
this.metrics.averageResponseTime =
|
|
119
|
+
this.metrics.averageResponseTime * 0.9 + duration * 0.1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
recordFailure() {
|
|
123
|
+
this.state.failureCount++;
|
|
124
|
+
this.state.lastFailureTime = Date.now();
|
|
125
|
+
if (this.state.failureCount === 1) {
|
|
126
|
+
this.state.state = 'half-open';
|
|
127
|
+
}
|
|
128
|
+
if (this.state.failureCount >= (this.options.threshold || 5)) {
|
|
129
|
+
this.setState('closed');
|
|
130
|
+
if (this.options.resetTimeout) {
|
|
131
|
+
this.resetTimer = setTimeout(() => {
|
|
132
|
+
this.reset();
|
|
133
|
+
}, this.options.resetTimeout);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
getMetrics() {
|
|
138
|
+
return {
|
|
139
|
+
...this.metrics,
|
|
140
|
+
currentState: this.state.state
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
reset() {
|
|
144
|
+
this.state = {
|
|
145
|
+
failureCount: 0,
|
|
146
|
+
lastFailureTime: 0,
|
|
147
|
+
state: 'open'
|
|
148
|
+
};
|
|
149
|
+
this.metrics = {
|
|
150
|
+
totalRequests: 0,
|
|
151
|
+
failedRequests: 0,
|
|
152
|
+
successfulRequests: 0,
|
|
153
|
+
currentState: 'open',
|
|
154
|
+
averageResponseTime: 0
|
|
155
|
+
};
|
|
156
|
+
if (this.resetTimer) {
|
|
157
|
+
clearTimeout(this.resetTimer);
|
|
158
|
+
this.resetTimer = undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
destroy() {
|
|
162
|
+
if (this.resetTimer) {
|
|
163
|
+
clearTimeout(this.resetTimer);
|
|
164
|
+
this.resetTimer = undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
isAsyncSupported() {
|
|
168
|
+
return this.baseTransport.isAsyncSupported?.() || false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.CircuitBreakerTransport = CircuitBreakerTransport;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DeadLetterQueue = void 0;
|
|
37
|
+
class DeadLetterQueue {
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.deadLetters = [];
|
|
40
|
+
const { transport, maxRetries = 3, retryableErrorCodes = ['ECONNREFUSED', 'ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND'], deadLetterFile, onDeadLetter, } = options;
|
|
41
|
+
// Initialize the wrapped transport
|
|
42
|
+
if (typeof transport === 'function') {
|
|
43
|
+
this.transport = transport();
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.transport = transport;
|
|
47
|
+
}
|
|
48
|
+
this.maxRetries = maxRetries;
|
|
49
|
+
this.retryableErrorCodes = new Set(retryableErrorCodes);
|
|
50
|
+
if (deadLetterFile)
|
|
51
|
+
this.deadLetterFile = deadLetterFile;
|
|
52
|
+
if (onDeadLetter)
|
|
53
|
+
this.onDeadLetter = onDeadLetter;
|
|
54
|
+
}
|
|
55
|
+
async write(data, formatter) {
|
|
56
|
+
return this.writeWithRetry(data, formatter, 0);
|
|
57
|
+
}
|
|
58
|
+
async writeAsync(data, formatter) {
|
|
59
|
+
return this.writeWithRetry(data, formatter, 0);
|
|
60
|
+
}
|
|
61
|
+
async writeWithRetry(data, formatter, attempt) {
|
|
62
|
+
try {
|
|
63
|
+
if (this.transport.writeAsync) {
|
|
64
|
+
await this.transport.writeAsync(data, formatter);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.transport.write(data, formatter);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const errorCode = error?.code || 'UNKNOWN';
|
|
72
|
+
// Check if this error is retryable
|
|
73
|
+
if (this.retryableErrorCodes.has(errorCode)) {
|
|
74
|
+
if (attempt < this.maxRetries) {
|
|
75
|
+
// Exponential backoff with jitter
|
|
76
|
+
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
|
|
77
|
+
const jitter = Math.random() * 0.1 * delay;
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, delay + jitter));
|
|
79
|
+
return this.writeWithRetry(data, formatter, attempt + 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// This is a permanent failure - create dead letter
|
|
83
|
+
const deadLetter = {
|
|
84
|
+
...data,
|
|
85
|
+
deadLetterReason: error instanceof Error ? error.message : String(error),
|
|
86
|
+
originalError: errorCode,
|
|
87
|
+
retryCount: attempt,
|
|
88
|
+
failedAt: new Date(),
|
|
89
|
+
};
|
|
90
|
+
this.deadLetters.push(deadLetter);
|
|
91
|
+
if (this.deadLetterFile) {
|
|
92
|
+
await this.writeDeadLetterToFile(deadLetter);
|
|
93
|
+
}
|
|
94
|
+
if (this.onDeadLetter) {
|
|
95
|
+
this.onDeadLetter(deadLetter);
|
|
96
|
+
}
|
|
97
|
+
// Re-throw the error for upstream handling
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async writeDeadLetterToFile(deadLetter) {
|
|
102
|
+
if (!this.deadLetterFile)
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
106
|
+
const deadLetterLine = JSON.stringify(deadLetter) + '\n';
|
|
107
|
+
await fs.promises.appendFile(this.deadLetterFile, deadLetterLine);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error('Failed to write dead letter:', error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
getDeadLetters() {
|
|
114
|
+
return [...this.deadLetters];
|
|
115
|
+
}
|
|
116
|
+
clearDeadLetters() {
|
|
117
|
+
this.deadLetters = [];
|
|
118
|
+
}
|
|
119
|
+
// Clean up resources
|
|
120
|
+
async destroy() {
|
|
121
|
+
if (this.transport && typeof this.transport.destroy === 'function') {
|
|
122
|
+
await this.transport.destroy();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
isAsyncSupported() {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.DeadLetterQueue = DeadLetterQueue;
|