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
|
@@ -42,17 +42,18 @@ const compressGzip = (0, util_1.promisify)(zlib.gzip);
|
|
|
42
42
|
const compressDeflate = (0, util_1.promisify)(zlib.deflate);
|
|
43
43
|
class FileTransport {
|
|
44
44
|
constructor(options) {
|
|
45
|
-
// batching funct
|
|
46
45
|
this.batchQueue = [];
|
|
47
46
|
this.batchTimer = null;
|
|
48
47
|
const { path: filePath, maxSize = 10 * 1024 * 1024, maxFiles = 5, compression = "none", batchInterval = 0, // no batching
|
|
49
|
-
compressOldFiles = true,
|
|
48
|
+
compressOldFiles = true, maxQueueSize = 10000, // default maximum queue size
|
|
49
|
+
} = options;
|
|
50
50
|
this.filePath = filePath;
|
|
51
51
|
this.maxSize = maxSize;
|
|
52
52
|
this.maxFiles = maxFiles;
|
|
53
53
|
this.compression = compression;
|
|
54
54
|
this.batchInterval = batchInterval;
|
|
55
55
|
this.compressOldFiles = compressOldFiles;
|
|
56
|
+
this.maxQueueSize = maxQueueSize;
|
|
56
57
|
const dir = path.dirname(this.filePath);
|
|
57
58
|
if (!fs.existsSync(dir)) {
|
|
58
59
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -69,7 +70,11 @@ class FileTransport {
|
|
|
69
70
|
const output = formatter.format(data);
|
|
70
71
|
const formattedOutput = output + "\n";
|
|
71
72
|
if (this.batchInterval > 0) {
|
|
72
|
-
// Queue entry if batching is enabled
|
|
73
|
+
// Queue entry if batching is enabled, with queue size limit
|
|
74
|
+
if (this.batchQueue.length >= this.maxQueueSize) {
|
|
75
|
+
// Drop oldest entry to maintain queue limit
|
|
76
|
+
this.batchQueue.shift();
|
|
77
|
+
}
|
|
73
78
|
this.batchQueue.push({
|
|
74
79
|
data: formattedOutput,
|
|
75
80
|
timestamp: new Date(),
|
|
@@ -86,6 +91,9 @@ class FileTransport {
|
|
|
86
91
|
async writeAsync(data, formatter) {
|
|
87
92
|
const formattedOutput = formatter.format(data) + "\n";
|
|
88
93
|
if (this.batchInterval > 0) {
|
|
94
|
+
if (this.batchQueue.length >= this.maxQueueSize) {
|
|
95
|
+
this.batchQueue.shift();
|
|
96
|
+
}
|
|
89
97
|
this.batchQueue.push({
|
|
90
98
|
data: formattedOutput,
|
|
91
99
|
timestamp: new Date(),
|
|
@@ -117,8 +125,7 @@ class FileTransport {
|
|
|
117
125
|
try {
|
|
118
126
|
if (!fs.existsSync(this.filePath))
|
|
119
127
|
return;
|
|
120
|
-
|
|
121
|
-
this.performRotation(currentContent, fs.writeFileSync);
|
|
128
|
+
this.performRotationWithStreams();
|
|
122
129
|
this.cleanupOldFiles();
|
|
123
130
|
}
|
|
124
131
|
catch (error) {
|
|
@@ -129,8 +136,7 @@ class FileTransport {
|
|
|
129
136
|
try {
|
|
130
137
|
if (!fs.existsSync(this.filePath))
|
|
131
138
|
return;
|
|
132
|
-
|
|
133
|
-
await this.performRotationAsync(currentContent);
|
|
139
|
+
await this.performRotationWithStreamsAsync();
|
|
134
140
|
await this.cleanupOldFilesAsync();
|
|
135
141
|
}
|
|
136
142
|
catch (error) {
|
|
@@ -161,6 +167,71 @@ class FileTransport {
|
|
|
161
167
|
}
|
|
162
168
|
await fs.promises.writeFile(this.filePath, "", "utf8");
|
|
163
169
|
}
|
|
170
|
+
performRotationWithStreams() {
|
|
171
|
+
const rotatedFilePath = this.getRotatedFilePath();
|
|
172
|
+
const readStream = fs.createReadStream(this.filePath);
|
|
173
|
+
if (this.compression !== "none" && this.compressOldFiles) {
|
|
174
|
+
const compressedFilePath = `${rotatedFilePath}.${this.compression === "gzip" ? "gz" : "zz"}`;
|
|
175
|
+
const writeStream = fs.createWriteStream(compressedFilePath);
|
|
176
|
+
const compressStream = this.compression === "gzip" ? zlib.createGzip() : zlib.createDeflate();
|
|
177
|
+
readStream.pipe(compressStream).pipe(writeStream);
|
|
178
|
+
writeStream.on('finish', () => {
|
|
179
|
+
fs.writeFileSync(this.filePath, "", "utf8");
|
|
180
|
+
});
|
|
181
|
+
writeStream.on('error', (error) => {
|
|
182
|
+
console.error("Error during stream compression:", error);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const writeStream = fs.createWriteStream(rotatedFilePath);
|
|
187
|
+
readStream.pipe(writeStream);
|
|
188
|
+
writeStream.on('finish', () => {
|
|
189
|
+
fs.writeFileSync(this.filePath, "", "utf8");
|
|
190
|
+
});
|
|
191
|
+
writeStream.on('error', (error) => {
|
|
192
|
+
console.error("Error during stream rotation:", error);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async performRotationWithStreamsAsync() {
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
const rotatedFilePath = this.getRotatedFilePath();
|
|
199
|
+
const readStream = fs.createReadStream(this.filePath);
|
|
200
|
+
if (this.compression !== "none" && this.compressOldFiles) {
|
|
201
|
+
const compressedFilePath = `${rotatedFilePath}.${this.compression === "gzip" ? "gz" : "zz"}`;
|
|
202
|
+
const writeStream = fs.createWriteStream(compressedFilePath);
|
|
203
|
+
const compressStream = this.compression === "gzip" ? zlib.createGzip() : zlib.createDeflate();
|
|
204
|
+
readStream.pipe(compressStream).pipe(writeStream);
|
|
205
|
+
writeStream.on('finish', async () => {
|
|
206
|
+
try {
|
|
207
|
+
await fs.promises.writeFile(this.filePath, "", "utf8");
|
|
208
|
+
resolve();
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
reject(error);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
writeStream.on('error', reject);
|
|
215
|
+
readStream.on('error', reject);
|
|
216
|
+
compressStream.on('error', reject);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const writeStream = fs.createWriteStream(rotatedFilePath);
|
|
220
|
+
readStream.pipe(writeStream);
|
|
221
|
+
writeStream.on('finish', async () => {
|
|
222
|
+
try {
|
|
223
|
+
await fs.promises.writeFile(this.filePath, "", "utf8");
|
|
224
|
+
resolve();
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
reject(error);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
writeStream.on('error', reject);
|
|
231
|
+
readStream.on('error', reject);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
164
235
|
getRotatedFilePath() {
|
|
165
236
|
const dir = path.dirname(this.filePath);
|
|
166
237
|
return path.join(dir, `${path.basename(this.filePath)}.${Date.now()}`);
|
|
@@ -39,7 +39,8 @@ const https = __importStar(require("https"));
|
|
|
39
39
|
const url = __importStar(require("url"));
|
|
40
40
|
class HttpTransport {
|
|
41
41
|
constructor(options) {
|
|
42
|
-
const { url, method = 'POST', headers = {}, timeout = 5000, retries = 3 // defaults
|
|
42
|
+
const { url, method = 'POST', headers = {}, timeout = 5000, retries = 3, // defaults
|
|
43
|
+
forceAsync = false // Force async mode even in write() method
|
|
43
44
|
} = options;
|
|
44
45
|
if (!url) {
|
|
45
46
|
throw new Error('HttpTransport requires a URL option');
|
|
@@ -49,6 +50,7 @@ class HttpTransport {
|
|
|
49
50
|
this.headers = { ...headers };
|
|
50
51
|
this.timeout = timeout;
|
|
51
52
|
this.retries = retries;
|
|
53
|
+
this.forceAsync = forceAsync;
|
|
52
54
|
// Set default Content-Type if not provided
|
|
53
55
|
if (!this.headers['Content-Type'] && !this.headers['content-type']) {
|
|
54
56
|
this.headers['Content-Type'] = 'application/json';
|
|
@@ -58,12 +60,22 @@ class HttpTransport {
|
|
|
58
60
|
// Format the data as JSON for HTTP transport
|
|
59
61
|
const logObject = this.parseFormattedData(data);
|
|
60
62
|
const body = JSON.stringify(logObject);
|
|
61
|
-
|
|
63
|
+
if (this.forceAsync) {
|
|
64
|
+
// Force async mode using setImmediate
|
|
65
|
+
setImmediate(() => {
|
|
66
|
+
this.sendHttpRequestWithRetry(body, 0)
|
|
67
|
+
.catch((error) => {
|
|
68
|
+
console.error('HttpTransport error (forced async mode):', error.message);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Best-effort synchronous mode - note: actual network I/O is still async
|
|
62
74
|
this.sendHttpRequestWithRetry(body, 0)
|
|
63
75
|
.catch((error) => {
|
|
64
76
|
console.error('HttpTransport error (sync mode):', error.message);
|
|
65
77
|
});
|
|
66
|
-
}
|
|
78
|
+
}
|
|
67
79
|
}
|
|
68
80
|
async writeAsync(data, formatter) {
|
|
69
81
|
// json formating for HttpTransport
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RetryTransport = exports.CircuitBreakerState = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
var CircuitBreakerState;
|
|
6
|
+
(function (CircuitBreakerState) {
|
|
7
|
+
CircuitBreakerState["CLOSED"] = "closed";
|
|
8
|
+
CircuitBreakerState["OPEN"] = "open";
|
|
9
|
+
CircuitBreakerState["HALF_OPEN"] = "half_open";
|
|
10
|
+
})(CircuitBreakerState || (exports.CircuitBreakerState = CircuitBreakerState = {}));
|
|
11
|
+
class RetryTransport extends events_1.EventEmitter {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super();
|
|
14
|
+
this.circuitBreakerState = CircuitBreakerState.CLOSED;
|
|
15
|
+
this.failureCount = 0;
|
|
16
|
+
this.circuitBreakerOpenTime = 0;
|
|
17
|
+
const { wrappedTransport, maxAttempts = 3, baseDelay = 1000, maxDelay = 30000, backoffMultiplier = 2, jitter = true, retryableErrorCodes = [
|
|
18
|
+
'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND',
|
|
19
|
+
'EAI_AGAIN', 'EHOSTUNREACH', 'ENETUNREACH', 'ENOENT',
|
|
20
|
+
'EMFILE', 'ENFILE'
|
|
21
|
+
], retryableErrorPatterns = [
|
|
22
|
+
/timeout/i,
|
|
23
|
+
/network/i,
|
|
24
|
+
/connection/i,
|
|
25
|
+
/temporary/i,
|
|
26
|
+
/rate limit/i,
|
|
27
|
+
/too many requests/i,
|
|
28
|
+
/service unavailable/i,
|
|
29
|
+
/bad gateway/i
|
|
30
|
+
], circuitBreakerThreshold = 5, circuitBreakerTimeout = 60000, onRetryAttempt, onRetryExhausted, onCircuitBreakerOpen, onCircuitBreakerClose } = options;
|
|
31
|
+
if (!wrappedTransport) {
|
|
32
|
+
throw new Error('RetryTransport requires a wrappedTransport');
|
|
33
|
+
}
|
|
34
|
+
this.wrappedTransport = wrappedTransport;
|
|
35
|
+
this.maxAttempts = maxAttempts;
|
|
36
|
+
this.baseDelay = baseDelay;
|
|
37
|
+
this.maxDelay = maxDelay;
|
|
38
|
+
this.backoffMultiplier = backoffMultiplier;
|
|
39
|
+
this.jitter = jitter;
|
|
40
|
+
this.retryableErrorCodes = new Set(retryableErrorCodes);
|
|
41
|
+
this.retryableErrorPatterns = retryableErrorPatterns;
|
|
42
|
+
this.circuitBreakerThreshold = circuitBreakerThreshold;
|
|
43
|
+
this.circuitBreakerTimeout = circuitBreakerTimeout;
|
|
44
|
+
this.onRetryAttempt = onRetryAttempt;
|
|
45
|
+
this.onRetryExhausted = onRetryExhausted;
|
|
46
|
+
this.onCircuitBreakerOpen = onCircuitBreakerOpen;
|
|
47
|
+
this.onCircuitBreakerClose = onCircuitBreakerClose;
|
|
48
|
+
}
|
|
49
|
+
write(data, formatter) {
|
|
50
|
+
setImmediate(async () => {
|
|
51
|
+
try {
|
|
52
|
+
await this.writeWithRetry(data, formatter);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
this.emit('error', { type: 'retry_transport_exhausted', error });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async writeAsync(data, formatter) {
|
|
60
|
+
return this.writeWithRetry(data, formatter);
|
|
61
|
+
}
|
|
62
|
+
async writeWithRetry(data, formatter) {
|
|
63
|
+
if (this.isCircuitBreakerOpen()) {
|
|
64
|
+
throw new Error('Circuit breaker is open - rejecting requests');
|
|
65
|
+
}
|
|
66
|
+
let lastError = null;
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
69
|
+
try {
|
|
70
|
+
if (this.wrappedTransport.writeAsync) {
|
|
71
|
+
await this.wrappedTransport.writeAsync(data, formatter);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
try {
|
|
76
|
+
this.wrappedTransport.write(data, formatter);
|
|
77
|
+
resolve();
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
reject(error);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
this.resetFailureCount();
|
|
85
|
+
this.maybeCloseCircuitBreaker();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
lastError = error;
|
|
90
|
+
if (!this.isRetryableError(lastError) || attempt === this.maxAttempts) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
const delay = this.calculateDelay(attempt);
|
|
94
|
+
const retryContext = {
|
|
95
|
+
attempt,
|
|
96
|
+
totalAttempts: this.maxAttempts,
|
|
97
|
+
originalError: lastError,
|
|
98
|
+
delay,
|
|
99
|
+
startTime
|
|
100
|
+
};
|
|
101
|
+
this.emit('retryAttempt', retryContext);
|
|
102
|
+
this.onRetryAttempt?.(attempt, lastError, delay);
|
|
103
|
+
await this.delay(delay);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
this.incrementFailureCount();
|
|
107
|
+
this.maybeOpenCircuitBreaker();
|
|
108
|
+
const errorContext = {
|
|
109
|
+
lastError,
|
|
110
|
+
attempts: this.maxAttempts,
|
|
111
|
+
totalTime: Date.now() - startTime,
|
|
112
|
+
data: {
|
|
113
|
+
level: data.level,
|
|
114
|
+
message: data.message,
|
|
115
|
+
timestamp: data.timestamp
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
this.emit('retryExhausted', errorContext);
|
|
119
|
+
this.onRetryExhausted?.(lastError, this.maxAttempts);
|
|
120
|
+
throw lastError;
|
|
121
|
+
}
|
|
122
|
+
isRetryableError(error) {
|
|
123
|
+
const errorCode = error.code;
|
|
124
|
+
const errorMessage = error.message;
|
|
125
|
+
if (errorCode && this.retryableErrorCodes.has(errorCode)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
for (const pattern of this.retryableErrorPatterns) {
|
|
129
|
+
if (pattern.test(errorMessage)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
calculateDelay(attempt) {
|
|
136
|
+
let delay = this.baseDelay * Math.pow(this.backoffMultiplier, attempt - 1);
|
|
137
|
+
delay = Math.min(delay, this.maxDelay);
|
|
138
|
+
if (this.jitter) {
|
|
139
|
+
const jitterAmount = delay * 0.25;
|
|
140
|
+
const jitter = (Math.random() * 2 - 1) * jitterAmount;
|
|
141
|
+
delay += jitter;
|
|
142
|
+
}
|
|
143
|
+
return Math.max(0, Math.floor(delay));
|
|
144
|
+
}
|
|
145
|
+
delay(ms) {
|
|
146
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
147
|
+
}
|
|
148
|
+
isCircuitBreakerOpen() {
|
|
149
|
+
if (this.circuitBreakerState === CircuitBreakerState.OPEN) {
|
|
150
|
+
if (Date.now() - this.circuitBreakerOpenTime >= this.circuitBreakerTimeout) {
|
|
151
|
+
this.circuitBreakerState = CircuitBreakerState.HALF_OPEN;
|
|
152
|
+
this.emit('circuitBreakerHalfOpen');
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
incrementFailureCount() {
|
|
160
|
+
this.failureCount++;
|
|
161
|
+
this.maybeOpenCircuitBreaker();
|
|
162
|
+
}
|
|
163
|
+
resetFailureCount() {
|
|
164
|
+
this.failureCount = 0;
|
|
165
|
+
}
|
|
166
|
+
maybeOpenCircuitBreaker() {
|
|
167
|
+
if (this.circuitBreakerState === CircuitBreakerState.CLOSED &&
|
|
168
|
+
this.failureCount >= this.circuitBreakerThreshold) {
|
|
169
|
+
this.circuitBreakerState = CircuitBreakerState.OPEN;
|
|
170
|
+
this.circuitBreakerOpenTime = Date.now();
|
|
171
|
+
this.emit('circuitBreakerOpen');
|
|
172
|
+
this.onCircuitBreakerOpen?.();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
maybeCloseCircuitBreaker() {
|
|
176
|
+
if (this.circuitBreakerState === CircuitBreakerState.HALF_OPEN) {
|
|
177
|
+
this.circuitBreakerState = CircuitBreakerState.CLOSED;
|
|
178
|
+
this.failureCount = 0;
|
|
179
|
+
this.emit('circuitBreakerClose');
|
|
180
|
+
this.onCircuitBreakerClose?.();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
getCircuitBreakerState() {
|
|
184
|
+
return this.circuitBreakerState;
|
|
185
|
+
}
|
|
186
|
+
getFailureCount() {
|
|
187
|
+
return this.failureCount;
|
|
188
|
+
}
|
|
189
|
+
resetCircuitBreaker() {
|
|
190
|
+
this.circuitBreakerState = CircuitBreakerState.CLOSED;
|
|
191
|
+
this.failureCount = 0;
|
|
192
|
+
this.circuitBreakerOpenTime = 0;
|
|
193
|
+
this.emit('circuitBreakerReset');
|
|
194
|
+
}
|
|
195
|
+
getWrappedTransport() {
|
|
196
|
+
return this.wrappedTransport;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.RetryTransport = RetryTransport;
|
|
@@ -19,3 +19,6 @@ __exportStar(require("./ConsoleTransport.js"), exports);
|
|
|
19
19
|
__exportStar(require("./FileTransport.js"), exports);
|
|
20
20
|
__exportStar(require("./HttpTransport.js"), exports);
|
|
21
21
|
__exportStar(require("./FilterableTransport.js"), exports);
|
|
22
|
+
__exportStar(require("./RetryTransport.js"), exports);
|
|
23
|
+
__exportStar(require("./CircuitBreakerTransport.js"), exports);
|
|
24
|
+
__exportStar(require("./DeadLetterQueue.js"), exports);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Timer = exports.TimeUtil = exports.ColorUtil = void 0;
|
|
4
|
+
class ColorUtil {
|
|
5
|
+
static colorize(text, color) {
|
|
6
|
+
const supportsColor = process.env.FORCE_COLOR !== "0" &&
|
|
7
|
+
(process.stdout.isTTY || process.env.FORCE_COLOR === "1");
|
|
8
|
+
if (!supportsColor) {
|
|
9
|
+
return text;
|
|
10
|
+
}
|
|
11
|
+
const colorCode = ColorUtil.ANSI_COLORS[color] || ColorUtil.ANSI_COLORS.reset;
|
|
12
|
+
return `${colorCode}${text}${ColorUtil.ANSI_COLORS.reset}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.ColorUtil = ColorUtil;
|
|
16
|
+
ColorUtil.ANSI_COLORS = {
|
|
17
|
+
black: "\x1b[30m",
|
|
18
|
+
red: "\x1b[31m",
|
|
19
|
+
green: "\x1b[32m",
|
|
20
|
+
yellow: "\x1b[33m",
|
|
21
|
+
blue: "\x1b[34m",
|
|
22
|
+
magenta: "\x1b[35m",
|
|
23
|
+
cyan: "\x1b[36m",
|
|
24
|
+
white: "\x1b[37m",
|
|
25
|
+
brightRed: "\x1b[91m",
|
|
26
|
+
brightGreen: "\x1b[92m",
|
|
27
|
+
brightYellow: "\x1b[93m",
|
|
28
|
+
brightBlue: "\x1b[94m",
|
|
29
|
+
brightMagenta: "\x1b[95m",
|
|
30
|
+
brightCyan: "\x1b[96m",
|
|
31
|
+
brightWhite: "\x1b[97m",
|
|
32
|
+
info: "\x1b[32m",
|
|
33
|
+
warn: "\x1b[33m",
|
|
34
|
+
error: "\x1b[31m",
|
|
35
|
+
debug: "\x1b[36m",
|
|
36
|
+
boring: "\x1b[37m",
|
|
37
|
+
reset: "\x1b[0m",
|
|
38
|
+
};
|
|
39
|
+
class TimeUtil {
|
|
40
|
+
static format(date, format) {
|
|
41
|
+
if (format === "ISO")
|
|
42
|
+
return date.toISOString();
|
|
43
|
+
if (format === "UTC")
|
|
44
|
+
return date.toUTCString();
|
|
45
|
+
if (format === "LOCAL")
|
|
46
|
+
return date.toLocaleString();
|
|
47
|
+
const pad = (n, width = 2) => n.toString().padStart(width, "0");
|
|
48
|
+
const tokens = {
|
|
49
|
+
YYYY: pad(date.getFullYear(), 4),
|
|
50
|
+
MM: pad(date.getMonth() + 1),
|
|
51
|
+
DD: pad(date.getDate()),
|
|
52
|
+
HH: pad(date.getHours()),
|
|
53
|
+
mm: pad(date.getMinutes()),
|
|
54
|
+
ss: pad(date.getSeconds()),
|
|
55
|
+
SSS: pad(date.getMilliseconds(), 3),
|
|
56
|
+
};
|
|
57
|
+
return format.replace(/YYYY|MM|DD|HH|mm|ss|SSS/g, (match) => tokens[match] || match);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.TimeUtil = TimeUtil;
|
|
61
|
+
class Timer {
|
|
62
|
+
constructor(name, logFn) {
|
|
63
|
+
this.hasEnded = false;
|
|
64
|
+
this.name = name;
|
|
65
|
+
this.logFn = logFn;
|
|
66
|
+
this.startTime = Date.now();
|
|
67
|
+
}
|
|
68
|
+
end() {
|
|
69
|
+
if (this.hasEnded) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const endTime = Date.now();
|
|
73
|
+
const duration = endTime - this.startTime;
|
|
74
|
+
this.logFn(`${this.name} took ${duration}ms`);
|
|
75
|
+
this.hasEnded = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.Timer = Timer;
|
|
@@ -1,57 +1,36 @@
|
|
|
1
|
-
import { LogData } from
|
|
2
|
-
import { Formatter } from
|
|
3
|
-
/**
|
|
4
|
-
* Interface for log aggregation targets
|
|
5
|
-
*/
|
|
1
|
+
import { LogData } from "../types/index.js";
|
|
2
|
+
import { Formatter } from "../core/Formatter.js";
|
|
6
3
|
export interface LogAggregator {
|
|
7
|
-
/**
|
|
8
|
-
* Process a log record for aggregation
|
|
9
|
-
* @param logData The structured log record
|
|
10
|
-
* @param formatter The formatter used for the log
|
|
11
|
-
*/
|
|
12
4
|
aggregate(logData: LogData, formatter: Formatter): void;
|
|
13
|
-
/**
|
|
14
|
-
* Flush any pending aggregated logs
|
|
15
|
-
*/
|
|
16
5
|
flush(): Promise<void> | void;
|
|
17
6
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Aggregates logs in memory and flushes them in batches
|
|
20
|
-
*/
|
|
21
7
|
export declare class BatchAggregator implements LogAggregator {
|
|
22
8
|
private logs;
|
|
23
9
|
private maxSize;
|
|
10
|
+
private maxQueueSize;
|
|
24
11
|
private flushCallback;
|
|
12
|
+
private pendingFlush;
|
|
25
13
|
constructor(maxSize: number | undefined, flushCallback: (logs: {
|
|
26
14
|
logData: LogData;
|
|
27
15
|
formatter: Formatter;
|
|
28
|
-
}[]) => Promise<void> | void);
|
|
16
|
+
}[]) => Promise<void> | void, maxQueueSize?: number);
|
|
29
17
|
aggregate(logData: LogData, formatter: Formatter): void;
|
|
30
18
|
flush(): Promise<void> | void;
|
|
31
19
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Aggregates logs based on a time interval
|
|
34
|
-
*/
|
|
35
20
|
export declare class TimeBasedAggregator implements LogAggregator {
|
|
36
21
|
private logs;
|
|
37
22
|
private flushInterval;
|
|
23
|
+
private maxQueueSize;
|
|
38
24
|
private flushCallback;
|
|
39
25
|
private timer;
|
|
40
|
-
constructor(flushInterval: number,
|
|
41
|
-
flushCallback: (logs: {
|
|
26
|
+
constructor(flushInterval: number, flushCallback: (logs: {
|
|
42
27
|
logData: LogData;
|
|
43
28
|
formatter: Formatter;
|
|
44
|
-
}[]) => Promise<void> | void);
|
|
29
|
+
}[]) => Promise<void> | void, maxQueueSize?: number);
|
|
45
30
|
aggregate(logData: LogData, formatter: Formatter): void;
|
|
46
31
|
flush(): Promise<void> | void;
|
|
47
|
-
/**
|
|
48
|
-
* Stop the aggregator and cancel any pending timer without flushing
|
|
49
|
-
*/
|
|
50
32
|
stop(): void;
|
|
51
33
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Combines multiple aggregators
|
|
54
|
-
*/
|
|
55
34
|
export declare class CompositeAggregator implements LogAggregator {
|
|
56
35
|
private aggregators;
|
|
57
36
|
constructor(aggregators: LogAggregator[]);
|
|
@@ -1,53 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
* Aggregates logs in memory and flushes them in batches
|
|
3
|
-
*/
|
|
1
|
+
//Aggregates logs in memory and flushes them in batches
|
|
4
2
|
export class BatchAggregator {
|
|
5
|
-
constructor(maxSize = 100, flushCallback) {
|
|
3
|
+
constructor(maxSize = 100, flushCallback, maxQueueSize = 10000) {
|
|
6
4
|
this.logs = [];
|
|
5
|
+
this.pendingFlush = null;
|
|
7
6
|
this.maxSize = maxSize;
|
|
7
|
+
this.maxQueueSize = maxQueueSize;
|
|
8
8
|
this.flushCallback = flushCallback;
|
|
9
9
|
}
|
|
10
10
|
aggregate(logData, formatter) {
|
|
11
|
+
if (this.logs.length >= this.maxQueueSize) {
|
|
12
|
+
this.logs.shift();
|
|
13
|
+
}
|
|
11
14
|
this.logs.push({ logData, formatter });
|
|
12
|
-
if (this.logs.length >= this.maxSize) {
|
|
15
|
+
if (this.logs.length >= this.maxSize && !this.pendingFlush) {
|
|
13
16
|
const result = this.flush();
|
|
14
|
-
// Handle the case where flush returns a Promise (async flushCallback)
|
|
15
17
|
if (result instanceof Promise) {
|
|
16
|
-
result.
|
|
17
|
-
|
|
18
|
+
this.pendingFlush = result.finally(() => {
|
|
19
|
+
this.pendingFlush = null;
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
flush() {
|
|
23
|
-
if (this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (this.pendingFlush) {
|
|
26
|
+
return this.pendingFlush;
|
|
27
|
+
}
|
|
28
|
+
if (this.logs.length === 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const logsToFlush = [...this.logs];
|
|
32
|
+
const originalLogs = [...this.logs];
|
|
33
|
+
this.logs = [];
|
|
34
|
+
try {
|
|
35
|
+
const callbackResult = this.flushCallback(logsToFlush);
|
|
36
|
+
if (callbackResult instanceof Promise) {
|
|
37
|
+
return callbackResult.catch((error) => {
|
|
38
|
+
this.logs = originalLogs;
|
|
39
|
+
throw error;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
this.logs = originalLogs;
|
|
45
|
+
throw error;
|
|
27
46
|
}
|
|
28
47
|
}
|
|
29
48
|
}
|
|
30
|
-
|
|
31
|
-
* Aggregates logs based on a time interval
|
|
32
|
-
*/
|
|
49
|
+
//Aggregates logs based on a time interval
|
|
33
50
|
export class TimeBasedAggregator {
|
|
34
|
-
constructor(flushInterval,
|
|
35
|
-
flushCallback) {
|
|
51
|
+
constructor(flushInterval, flushCallback, maxQueueSize = 10000) {
|
|
36
52
|
this.logs = [];
|
|
37
53
|
this.timer = null;
|
|
38
54
|
this.flushInterval = flushInterval;
|
|
55
|
+
this.maxQueueSize = maxQueueSize;
|
|
39
56
|
this.flushCallback = flushCallback;
|
|
40
57
|
}
|
|
41
58
|
aggregate(logData, formatter) {
|
|
59
|
+
if (this.logs.length >= this.maxQueueSize) {
|
|
60
|
+
this.logs.shift();
|
|
61
|
+
}
|
|
42
62
|
this.logs.push({ logData, formatter });
|
|
43
|
-
// Start
|
|
63
|
+
// Start timer if it's not already running
|
|
44
64
|
if (!this.timer) {
|
|
45
65
|
this.timer = setTimeout(() => {
|
|
46
66
|
const result = this.flush();
|
|
47
|
-
// Handle
|
|
67
|
+
// Handle case where flush returns a Promise (async flushCallback)
|
|
48
68
|
if (result instanceof Promise) {
|
|
49
|
-
result.catch(error => {
|
|
50
|
-
console.error(
|
|
69
|
+
result.catch((error) => {
|
|
70
|
+
console.error("Error in TimeBasedAggregator flush callback:", error);
|
|
51
71
|
});
|
|
52
72
|
}
|
|
53
73
|
}, this.flushInterval);
|
|
@@ -55,19 +75,30 @@ export class TimeBasedAggregator {
|
|
|
55
75
|
}
|
|
56
76
|
flush() {
|
|
57
77
|
if (this.logs.length > 0) {
|
|
58
|
-
// Clear
|
|
78
|
+
// Clear timer if it exists
|
|
59
79
|
if (this.timer) {
|
|
60
80
|
clearTimeout(this.timer);
|
|
61
81
|
this.timer = null;
|
|
62
82
|
}
|
|
63
83
|
const logsToFlush = [...this.logs];
|
|
84
|
+
const originalLogs = [...this.logs];
|
|
64
85
|
this.logs = [];
|
|
65
|
-
|
|
86
|
+
try {
|
|
87
|
+
const callbackResult = this.flushCallback(logsToFlush);
|
|
88
|
+
if (callbackResult instanceof Promise) {
|
|
89
|
+
return callbackResult.catch((error) => {
|
|
90
|
+
this.logs = originalLogs;
|
|
91
|
+
throw error;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.logs = originalLogs;
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
66
99
|
}
|
|
67
100
|
}
|
|
68
|
-
|
|
69
|
-
* Stop the aggregator and cancel any pending timer without flushing
|
|
70
|
-
*/
|
|
101
|
+
//Stop aggregator and cancel any pending timer without flushing
|
|
71
102
|
stop() {
|
|
72
103
|
if (this.timer) {
|
|
73
104
|
clearTimeout(this.timer);
|
|
@@ -75,9 +106,7 @@ export class TimeBasedAggregator {
|
|
|
75
106
|
}
|
|
76
107
|
}
|
|
77
108
|
}
|
|
78
|
-
|
|
79
|
-
* Combines multiple aggregators
|
|
80
|
-
*/
|
|
109
|
+
//Combines multiple aggregators
|
|
81
110
|
export class CompositeAggregator {
|
|
82
111
|
constructor(aggregators) {
|
|
83
112
|
this.aggregators = aggregators;
|
|
@@ -96,8 +125,8 @@ export class CompositeAggregator {
|
|
|
96
125
|
}
|
|
97
126
|
}
|
|
98
127
|
// If any aggregator returns a promise, wait for all of them
|
|
99
|
-
if (results.some(r => r instanceof Promise)) {
|
|
100
|
-
const promiseResults = results.filter(r => r instanceof Promise);
|
|
128
|
+
if (results.some((r) => r instanceof Promise)) {
|
|
129
|
+
const promiseResults = results.filter((r) => r instanceof Promise);
|
|
101
130
|
return Promise.all(promiseResults).then(() => { });
|
|
102
131
|
}
|
|
103
132
|
}
|