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
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { TimeUtil } from "../utils/
|
|
2
|
-
import { ColorUtil } from "../utils/ColorUtil.js";
|
|
1
|
+
import { TimeUtil, ColorUtil } from "../utils/index.js";
|
|
3
2
|
export class Formatter {
|
|
4
3
|
constructor(options = {}) {
|
|
5
4
|
const { colorize = true, json = false, timestampFormat = "YYYY-MM-DD HH:mm:ss", timestamp = false, customColors = {}, } = options;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { LogLevel } from "./LogLevel.js";
|
|
2
2
|
import { Transport } from "../transports/Transport.js";
|
|
3
|
+
import { RetryTransportOptions } from "../transports/RetryTransport.js";
|
|
3
4
|
import { TransportConfig } from "../types/index.js";
|
|
4
5
|
import { Filter } from "../filters/Filter.js";
|
|
5
6
|
import { LogAggregator } from "../aggregation/LogAggregator.js";
|
|
6
7
|
import { LogEnricher, LogEnrichmentPipeline } from "../structured/StructuredExtensions.js";
|
|
8
|
+
import { Timer } from "../utils/index.js";
|
|
9
|
+
import { EventEmitter } from "events";
|
|
7
10
|
export interface LoggerOptions {
|
|
8
11
|
level?: LogLevel;
|
|
9
12
|
colorize?: boolean;
|
|
@@ -12,9 +15,10 @@ export interface LoggerOptions {
|
|
|
12
15
|
timestampFormat?: string;
|
|
13
16
|
prefix?: string;
|
|
14
17
|
timestamp?: boolean;
|
|
15
|
-
context?: Record<string,
|
|
18
|
+
context?: Record<string, unknown>;
|
|
16
19
|
parent?: Logger;
|
|
17
20
|
asyncMode?: boolean;
|
|
21
|
+
async?: boolean;
|
|
18
22
|
customLevels?: {
|
|
19
23
|
[level: string]: number;
|
|
20
24
|
};
|
|
@@ -24,8 +28,10 @@ export interface LoggerOptions {
|
|
|
24
28
|
filters?: Filter[];
|
|
25
29
|
aggregators?: LogAggregator[];
|
|
26
30
|
enrichers?: LogEnrichmentPipeline;
|
|
31
|
+
deadLetterQueue?: any;
|
|
32
|
+
retryOptions?: RetryTransportOptions;
|
|
27
33
|
}
|
|
28
|
-
export declare class Logger {
|
|
34
|
+
export declare class Logger extends EventEmitter {
|
|
29
35
|
private level;
|
|
30
36
|
private transports;
|
|
31
37
|
private formatter;
|
|
@@ -36,6 +42,8 @@ export declare class Logger {
|
|
|
36
42
|
private filters;
|
|
37
43
|
private aggregators;
|
|
38
44
|
private enrichers;
|
|
45
|
+
private deadLetterQueue?;
|
|
46
|
+
private retryOptions;
|
|
39
47
|
private static _global;
|
|
40
48
|
static defaultTransportsFactory: ((isProd: boolean) => TransportConfig[]) | null;
|
|
41
49
|
private static readonly LEVEL_PRIORITIES;
|
|
@@ -58,6 +66,7 @@ export declare class Logger {
|
|
|
58
66
|
info(message: string, metadata?: Record<string, any>): void;
|
|
59
67
|
warn(message: string, metadata?: Record<string, any>): void;
|
|
60
68
|
error(message: string, metadata?: Record<string, any>): void;
|
|
69
|
+
fatal(message: string, metadata?: Record<string, any>): void;
|
|
61
70
|
silent(message: string, metadata?: Record<string, any>): void;
|
|
62
71
|
boring(message: string, metadata?: Record<string, any>): void;
|
|
63
72
|
/**
|
|
@@ -71,7 +80,7 @@ export declare class Logger {
|
|
|
71
80
|
getTimestampSetting(): boolean;
|
|
72
81
|
static get global(): Logger;
|
|
73
82
|
createChild(options?: LoggerOptions): Logger;
|
|
74
|
-
startTimer(name: string):
|
|
83
|
+
startTimer(name: string): Timer;
|
|
75
84
|
/**
|
|
76
85
|
* Add a filter to the logger
|
|
77
86
|
*/
|
|
@@ -96,4 +105,5 @@ export declare class Logger {
|
|
|
96
105
|
* Flush all aggregators
|
|
97
106
|
*/
|
|
98
107
|
flushAggregators(): Promise<void>;
|
|
108
|
+
getTransports(): Transport[];
|
|
99
109
|
}
|
package/dist/esm/core/Logger.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import { Formatter } from "./Formatter.js";
|
|
2
2
|
import { ConsoleTransport } from "../transports/ConsoleTransport.js";
|
|
3
|
+
import { RetryTransport } from "../transports/RetryTransport.js";
|
|
3
4
|
import { LogEnrichmentPipeline } from "../structured/StructuredExtensions.js";
|
|
4
|
-
|
|
5
|
+
import { Timer } from "../utils/index.js";
|
|
6
|
+
import { EventEmitter } from "events";
|
|
7
|
+
export class Logger extends EventEmitter {
|
|
5
8
|
constructor(options = {}) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
this.
|
|
9
|
-
const { level, colorize, json, transports = [], timestampFormat = "YYYY-MM-DD HH:mm:ss", prefix, timestamp, context = {}, parent, asyncMode, customLevels = {}, customColors = {}, filters = [], aggregators = [], enrichers, } = options;
|
|
10
|
-
this.parent = parent; // Set parent
|
|
9
|
+
const { level, colorize, json, transports = [], timestampFormat = "YYYY-MM-DD HH:mm:ss", prefix, timestamp, context = {}, parent, asyncMode, async, customLevels = {}, customColors = {}, filters = [], aggregators = [], enrichers, } = options;
|
|
10
|
+
super();
|
|
11
|
+
this.parent = parent;
|
|
11
12
|
this.context = { ...context }; // Init context
|
|
12
13
|
this.customLevels = customLevels; // custom log store
|
|
13
14
|
this.asyncMode = false;
|
|
14
15
|
this.filters = [...filters]; // Copy filters
|
|
15
16
|
this.aggregators = [...aggregators]; // Copy aggregators
|
|
16
|
-
this.enrichers = enrichers ?? new LogEnrichmentPipeline();
|
|
17
|
+
this.enrichers = enrichers ?? new LogEnrichmentPipeline();
|
|
18
|
+
this.deadLetterQueue = options.deadLetterQueue;
|
|
19
|
+
this.retryOptions = options.retryOptions;
|
|
17
20
|
if (this.parent) {
|
|
18
21
|
this.level = level ?? this.parent.level;
|
|
19
22
|
this.prefix = prefix ?? this.parent.prefix;
|
|
20
23
|
this.timestamp = timestamp ?? this.parent.timestamp;
|
|
21
|
-
this.asyncMode = asyncMode ?? this.parent.asyncMode;
|
|
24
|
+
this.asyncMode = (async ?? asyncMode) ?? this.parent.asyncMode;
|
|
22
25
|
this.transports =
|
|
23
26
|
transports && transports.length > 0
|
|
24
27
|
? this.initTransports(transports)
|
|
@@ -64,7 +67,7 @@ export class Logger {
|
|
|
64
67
|
const defaultTransports = transports && transports.length > 0
|
|
65
68
|
? transports
|
|
66
69
|
: this.getDefaultTransports(isProd);
|
|
67
|
-
this.asyncMode = asyncMode ?? this.getDefaultAsyncMode(isProd);
|
|
70
|
+
this.asyncMode = (async ?? asyncMode) ?? this.getDefaultAsyncMode(isProd);
|
|
68
71
|
this.transports = this.initTransports(defaultTransports);
|
|
69
72
|
this.formatter = new Formatter({
|
|
70
73
|
colorize: this.getDefaultColorizeValue(colorize),
|
|
@@ -111,7 +114,15 @@ export class Logger {
|
|
|
111
114
|
const initializedTransports = [];
|
|
112
115
|
for (const transportConfig of transportConfigs) {
|
|
113
116
|
if (this.isTransport(transportConfig)) {
|
|
114
|
-
|
|
117
|
+
let transport = transportConfig;
|
|
118
|
+
// Check if transport is already a RetryTransport to avoid double-wrapping
|
|
119
|
+
if (this.retryOptions && !(transport instanceof RetryTransport)) {
|
|
120
|
+
transport = new RetryTransport({
|
|
121
|
+
...this.retryOptions,
|
|
122
|
+
wrappedTransport: transport
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
initializedTransports.push(transport);
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
return initializedTransports;
|
|
@@ -168,7 +179,14 @@ export class Logger {
|
|
|
168
179
|
prefix: this.prefix,
|
|
169
180
|
};
|
|
170
181
|
// Apply enrichers to the log data
|
|
171
|
-
|
|
182
|
+
try {
|
|
183
|
+
logData = this.enrichers.process(logData);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error('Error in enrichers:', error);
|
|
187
|
+
this.emit('error', { type: 'enricher', error });
|
|
188
|
+
// Continue with original logData if enrichment fails
|
|
189
|
+
}
|
|
172
190
|
// Check if the log should be emitted based on filters
|
|
173
191
|
// Use a copy to prevent concurrent modification issues if filters are modified during logging
|
|
174
192
|
const currentFilters = [...this.filters];
|
|
@@ -183,6 +201,7 @@ export class Logger {
|
|
|
183
201
|
if (transport.writeAsync) {
|
|
184
202
|
transport.writeAsync(logData, this.formatter).catch((error) => {
|
|
185
203
|
console.error("Error during async logging:", error);
|
|
204
|
+
this.emit('error', { type: 'transport', error });
|
|
186
205
|
});
|
|
187
206
|
}
|
|
188
207
|
else {
|
|
@@ -205,6 +224,7 @@ export class Logger {
|
|
|
205
224
|
}
|
|
206
225
|
catch (error) {
|
|
207
226
|
console.error('Error in aggregator:', error);
|
|
227
|
+
this.emit('error', { type: 'aggregator', error });
|
|
208
228
|
}
|
|
209
229
|
}
|
|
210
230
|
}
|
|
@@ -221,6 +241,9 @@ export class Logger {
|
|
|
221
241
|
error(message, metadata) {
|
|
222
242
|
this.log("error", message, metadata);
|
|
223
243
|
}
|
|
244
|
+
fatal(message, metadata) {
|
|
245
|
+
this.log("fatal", message, metadata);
|
|
246
|
+
}
|
|
224
247
|
silent(message, metadata) {
|
|
225
248
|
this.log("silent", message, metadata);
|
|
226
249
|
}
|
|
@@ -258,7 +281,6 @@ export class Logger {
|
|
|
258
281
|
return new Logger({ ...options, parent: this });
|
|
259
282
|
}
|
|
260
283
|
startTimer(name) {
|
|
261
|
-
const { Timer } = require("../utils/Timerutil");
|
|
262
284
|
return new Timer(name, (message) => this.info(message));
|
|
263
285
|
}
|
|
264
286
|
/**
|
|
@@ -307,21 +329,16 @@ export class Logger {
|
|
|
307
329
|
async flushAggregators() {
|
|
308
330
|
const flushPromises = [];
|
|
309
331
|
for (const aggregator of this.aggregators) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// Wrap the promise to catch and log rejections, so one failure doesn't stop others
|
|
314
|
-
flushPromises.push(result.catch(error => {
|
|
315
|
-
console.error('Error flushing aggregator:', error);
|
|
316
|
-
}));
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
console.error('Error flushing aggregator:', error);
|
|
332
|
+
const result = aggregator.flush();
|
|
333
|
+
if (result instanceof Promise) {
|
|
334
|
+
flushPromises.push(result);
|
|
321
335
|
}
|
|
322
336
|
}
|
|
323
337
|
await Promise.all(flushPromises);
|
|
324
338
|
}
|
|
339
|
+
getTransports() {
|
|
340
|
+
return this.transports;
|
|
341
|
+
}
|
|
325
342
|
}
|
|
326
343
|
Logger.defaultTransportsFactory = null;
|
|
327
344
|
Logger.LEVEL_PRIORITIES = {
|
|
@@ -1,47 +1,49 @@
|
|
|
1
1
|
import { LogData } from '../types/index.js';
|
|
2
|
+
import { LogLevel } from '../core/LogLevel.js';
|
|
2
3
|
export interface Filter {
|
|
3
|
-
/**
|
|
4
|
-
* Determines if a log record should be emitted
|
|
5
|
-
* @param logData The structured log record
|
|
6
|
-
* @returns true if the log should be emitted, false if it should be filtered out
|
|
7
|
-
*/
|
|
8
4
|
shouldEmit(logData: LogData): boolean;
|
|
9
5
|
}
|
|
10
6
|
export type FilterPredicate = (logData: LogData) => boolean;
|
|
11
|
-
/**
|
|
12
|
-
* A filter that combines multiple filters with AND logic
|
|
13
|
-
* Note: With an empty array of filters, this returns true (allows all logs).
|
|
14
|
-
* This follows the mathematical concept of vacuous truth - "all" conditions
|
|
15
|
-
* are satisfied when there are no conditions to check.
|
|
16
|
-
*/
|
|
17
7
|
export declare class CompositeFilter implements Filter {
|
|
18
8
|
private filters;
|
|
19
9
|
constructor(filters: Filter[]);
|
|
20
10
|
shouldEmit(logData: LogData): boolean;
|
|
21
11
|
}
|
|
22
|
-
/**
|
|
23
|
-
* A filter that combines multiple filters with OR logic
|
|
24
|
-
* Note: With an empty array of filters, this returns false (blocks all logs).
|
|
25
|
-
* This is because there are no matching conditions when the filter array is empty.
|
|
26
|
-
*/
|
|
27
12
|
export declare class OrFilter implements Filter {
|
|
28
13
|
private filters;
|
|
29
14
|
constructor(filters: Filter[]);
|
|
30
15
|
shouldEmit(logData: LogData): boolean;
|
|
31
16
|
}
|
|
32
|
-
/**
|
|
33
|
-
* A filter that negates another filter
|
|
34
|
-
*/
|
|
35
17
|
export declare class NotFilter implements Filter {
|
|
36
18
|
private filter;
|
|
37
19
|
constructor(filter: Filter);
|
|
38
20
|
shouldEmit(logData: LogData): boolean;
|
|
39
21
|
}
|
|
40
|
-
/**
|
|
41
|
-
* A filter based on a predicate function
|
|
42
|
-
*/
|
|
43
22
|
export declare class PredicateFilter implements Filter {
|
|
44
23
|
private predicate;
|
|
45
24
|
constructor(predicate: FilterPredicate);
|
|
46
25
|
shouldEmit(logData: LogData): boolean;
|
|
47
26
|
}
|
|
27
|
+
export declare class LevelFilter implements Filter {
|
|
28
|
+
private allowedLevels;
|
|
29
|
+
constructor(allowedLevels: LogLevel[]);
|
|
30
|
+
shouldEmit(logData: LogData): boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare class PrefixFilter implements Filter {
|
|
33
|
+
private allowedPrefixes;
|
|
34
|
+
constructor(allowedPrefixes: string[]);
|
|
35
|
+
shouldEmit(logData: LogData): boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare class MetadataFilter implements Filter {
|
|
38
|
+
private requiredMetadata;
|
|
39
|
+
constructor(requiredMetadata: {
|
|
40
|
+
[key: string]: any;
|
|
41
|
+
});
|
|
42
|
+
shouldEmit(logData: LogData): boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare class FieldFilter implements Filter {
|
|
45
|
+
private fieldName;
|
|
46
|
+
private expectedValue;
|
|
47
|
+
constructor(fieldName: string, expectedValue: any);
|
|
48
|
+
shouldEmit(logData: LogData): boolean;
|
|
49
|
+
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A filter that combines multiple filters with AND logic
|
|
3
|
-
* Note: With an empty array of filters, this returns true (allows all logs).
|
|
4
|
-
* This follows the mathematical concept of vacuous truth - "all" conditions
|
|
5
|
-
* are satisfied when there are no conditions to check.
|
|
6
|
-
*/
|
|
7
1
|
export class CompositeFilter {
|
|
8
2
|
constructor(filters) {
|
|
9
3
|
this.filters = filters;
|
|
@@ -12,11 +6,6 @@ export class CompositeFilter {
|
|
|
12
6
|
return this.filters.every(filter => filter.shouldEmit(logData));
|
|
13
7
|
}
|
|
14
8
|
}
|
|
15
|
-
/**
|
|
16
|
-
* A filter that combines multiple filters with OR logic
|
|
17
|
-
* Note: With an empty array of filters, this returns false (blocks all logs).
|
|
18
|
-
* This is because there are no matching conditions when the filter array is empty.
|
|
19
|
-
*/
|
|
20
9
|
export class OrFilter {
|
|
21
10
|
constructor(filters) {
|
|
22
11
|
this.filters = filters;
|
|
@@ -25,9 +14,6 @@ export class OrFilter {
|
|
|
25
14
|
return this.filters.some(filter => filter.shouldEmit(logData));
|
|
26
15
|
}
|
|
27
16
|
}
|
|
28
|
-
/**
|
|
29
|
-
* A filter that negates another filter
|
|
30
|
-
*/
|
|
31
17
|
export class NotFilter {
|
|
32
18
|
constructor(filter) {
|
|
33
19
|
this.filter = filter;
|
|
@@ -36,9 +22,6 @@ export class NotFilter {
|
|
|
36
22
|
return !this.filter.shouldEmit(logData);
|
|
37
23
|
}
|
|
38
24
|
}
|
|
39
|
-
/**
|
|
40
|
-
* A filter based on a predicate function
|
|
41
|
-
*/
|
|
42
25
|
export class PredicateFilter {
|
|
43
26
|
constructor(predicate) {
|
|
44
27
|
this.predicate = predicate;
|
|
@@ -47,3 +30,50 @@ export class PredicateFilter {
|
|
|
47
30
|
return this.predicate(logData);
|
|
48
31
|
}
|
|
49
32
|
}
|
|
33
|
+
export class LevelFilter {
|
|
34
|
+
constructor(allowedLevels) {
|
|
35
|
+
this.allowedLevels = new Set(allowedLevels);
|
|
36
|
+
}
|
|
37
|
+
shouldEmit(logData) {
|
|
38
|
+
return this.allowedLevels.has(logData.level);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class PrefixFilter {
|
|
42
|
+
constructor(allowedPrefixes) {
|
|
43
|
+
this.allowedPrefixes = new Set(allowedPrefixes);
|
|
44
|
+
}
|
|
45
|
+
shouldEmit(logData) {
|
|
46
|
+
if (!logData.prefix) {
|
|
47
|
+
return this.allowedPrefixes.has('');
|
|
48
|
+
}
|
|
49
|
+
return this.allowedPrefixes.has(logData.prefix);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class MetadataFilter {
|
|
53
|
+
constructor(requiredMetadata) {
|
|
54
|
+
this.requiredMetadata = requiredMetadata;
|
|
55
|
+
}
|
|
56
|
+
shouldEmit(logData) {
|
|
57
|
+
if (!logData.metadata) {
|
|
58
|
+
return Object.keys(this.requiredMetadata).length === 0;
|
|
59
|
+
}
|
|
60
|
+
for (const [key, value] of Object.entries(this.requiredMetadata)) {
|
|
61
|
+
if (logData.metadata[key] !== value) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export class FieldFilter {
|
|
69
|
+
constructor(fieldName, expectedValue) {
|
|
70
|
+
this.fieldName = fieldName;
|
|
71
|
+
this.expectedValue = expectedValue;
|
|
72
|
+
}
|
|
73
|
+
shouldEmit(logData) {
|
|
74
|
+
if (!logData.metadata) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return logData.metadata[this.fieldName] === this.expectedValue;
|
|
78
|
+
}
|
|
79
|
+
}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Logger } from './core/Logger.js';
|
|
2
2
|
import { LogLevel } from './core/LogLevel.js';
|
|
3
|
-
import { ConsoleTransport, FileTransport, HttpTransport, Transport, FilterableTransport } from './transports/index.js';
|
|
3
|
+
import { ConsoleTransport, FileTransport, HttpTransport, Transport, FilterableTransport, RetryTransport, CircuitBreakerTransport, DeadLetterQueue } from './transports/index.js';
|
|
4
4
|
import { TransportConfig, LoggerConfig } from './types/index.js';
|
|
5
5
|
import { CustomLogLevelConfig } from './core/CustomLogLevel.js';
|
|
6
6
|
import { Filter, CompositeFilter, OrFilter, NotFilter, PredicateFilter, LevelFilter, PrefixFilter, MetadataFilter, FieldFilter } from './filters/index.js';
|
|
7
7
|
import { LogAggregator, BatchAggregator, TimeBasedAggregator, CompositeAggregator } from './aggregation/index.js';
|
|
8
8
|
import { LogEnricher, MetadataEnricher, LogEnrichmentPipeline } from './structured/index.js';
|
|
9
|
-
|
|
9
|
+
import { Timer } from './utils/index.js';
|
|
10
|
+
export { Logger, ConsoleTransport, FileTransport, HttpTransport, FilterableTransport, RetryTransport, CircuitBreakerTransport, DeadLetterQueue, CompositeFilter, OrFilter, NotFilter, PredicateFilter, LevelFilter, PrefixFilter, MetadataFilter, FieldFilter, BatchAggregator, TimeBasedAggregator, CompositeAggregator, MetadataEnricher, LogEnrichmentPipeline, Timer };
|
|
10
11
|
export type { LogLevel, Transport, TransportConfig, LoggerConfig, CustomLogLevelConfig, Filter, LogAggregator, LogEnricher };
|
|
11
12
|
export default Logger;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Logger } from './core/Logger.js';
|
|
2
|
-
import { ConsoleTransport, FileTransport, HttpTransport, FilterableTransport } from './transports/index.js';
|
|
2
|
+
import { ConsoleTransport, FileTransport, HttpTransport, FilterableTransport, RetryTransport, CircuitBreakerTransport, DeadLetterQueue } from './transports/index.js';
|
|
3
3
|
import { CompositeFilter, OrFilter, NotFilter, PredicateFilter, LevelFilter, PrefixFilter, MetadataFilter, FieldFilter } from './filters/index.js';
|
|
4
4
|
import { BatchAggregator, TimeBasedAggregator, CompositeAggregator } from './aggregation/index.js';
|
|
5
5
|
import { MetadataEnricher, LogEnrichmentPipeline } from './structured/index.js';
|
|
6
|
+
import { Timer } from './utils/index.js';
|
|
6
7
|
// Configure default transports to maintain backward compatibility
|
|
7
8
|
Logger.defaultTransportsFactory = (isProd) => {
|
|
8
9
|
if (isProd) {
|
|
@@ -12,11 +13,13 @@ Logger.defaultTransportsFactory = (isProd) => {
|
|
|
12
13
|
return [new ConsoleTransport()];
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
|
-
export { Logger, ConsoleTransport, FileTransport, HttpTransport, FilterableTransport,
|
|
16
|
+
export { Logger, ConsoleTransport, FileTransport, HttpTransport, FilterableTransport, RetryTransport, CircuitBreakerTransport, DeadLetterQueue,
|
|
16
17
|
// Filters
|
|
17
18
|
CompositeFilter, OrFilter, NotFilter, PredicateFilter, LevelFilter, PrefixFilter, MetadataFilter, FieldFilter,
|
|
18
19
|
// Aggregators
|
|
19
20
|
BatchAggregator, TimeBasedAggregator, CompositeAggregator,
|
|
20
21
|
// Structured logging extensions
|
|
21
|
-
MetadataEnricher, LogEnrichmentPipeline
|
|
22
|
+
MetadataEnricher, LogEnrichmentPipeline,
|
|
23
|
+
// Utils
|
|
24
|
+
Timer };
|
|
22
25
|
export default Logger;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Transport } from "../transports/Transport.js";
|
|
2
|
+
import { TransportConfig, LogData } from "../types/index.js";
|
|
3
|
+
import { Formatter } from "../core/Formatter.js";
|
|
4
|
+
export interface CircuitBreakerOptions {
|
|
5
|
+
threshold?: number;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
resetTimeout?: number;
|
|
8
|
+
onStateChange?: (fromState: string, toState: string) => void;
|
|
9
|
+
onTrip?: (failureCount: number) => void;
|
|
10
|
+
onReset?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface CircuitBreakerStateInfo {
|
|
13
|
+
failureCount: number;
|
|
14
|
+
lastFailureTime: number;
|
|
15
|
+
state: 'closed' | 'half-open' | 'open';
|
|
16
|
+
}
|
|
17
|
+
export interface CircuitBreakerMetrics {
|
|
18
|
+
totalRequests: number;
|
|
19
|
+
failedRequests: number;
|
|
20
|
+
successfulRequests: number;
|
|
21
|
+
currentState: string;
|
|
22
|
+
averageResponseTime: number;
|
|
23
|
+
}
|
|
24
|
+
export declare class CircuitBreakerTransport implements Transport {
|
|
25
|
+
private baseTransport;
|
|
26
|
+
private state;
|
|
27
|
+
private metrics;
|
|
28
|
+
private options;
|
|
29
|
+
private resetTimer;
|
|
30
|
+
constructor(baseTransport: TransportConfig, options?: CircuitBreakerOptions);
|
|
31
|
+
private createTransport;
|
|
32
|
+
write(data: LogData, formatter: Formatter): void;
|
|
33
|
+
writeAsync(data: LogData, formatter: Formatter): Promise<void>;
|
|
34
|
+
private canWrite;
|
|
35
|
+
private setState;
|
|
36
|
+
private resetFailureCount;
|
|
37
|
+
private updateAverageResponseTime;
|
|
38
|
+
private recordFailure;
|
|
39
|
+
getMetrics(): CircuitBreakerMetrics;
|
|
40
|
+
reset(): void;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
isAsyncSupported(): boolean;
|
|
43
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export class CircuitBreakerTransport {
|
|
2
|
+
constructor(baseTransport, options = {}) {
|
|
3
|
+
this.baseTransport = this.createTransport(baseTransport);
|
|
4
|
+
this.options = options;
|
|
5
|
+
this.state = {
|
|
6
|
+
failureCount: 0,
|
|
7
|
+
lastFailureTime: 0,
|
|
8
|
+
state: 'open'
|
|
9
|
+
};
|
|
10
|
+
this.metrics = {
|
|
11
|
+
totalRequests: 0,
|
|
12
|
+
failedRequests: 0,
|
|
13
|
+
successfulRequests: 0,
|
|
14
|
+
currentState: 'open',
|
|
15
|
+
averageResponseTime: 0
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
createTransport(config) {
|
|
19
|
+
if (typeof config === 'function') {
|
|
20
|
+
const TransportClass = config;
|
|
21
|
+
return new TransportClass();
|
|
22
|
+
}
|
|
23
|
+
else if (typeof config === 'object' && config !== null) {
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw new Error('Invalid transport configuration');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
write(data, formatter) {
|
|
31
|
+
if (!this.canWrite()) {
|
|
32
|
+
throw new Error('Circuit breaker is open');
|
|
33
|
+
}
|
|
34
|
+
this.metrics.totalRequests++;
|
|
35
|
+
const startTime = Date.now();
|
|
36
|
+
try {
|
|
37
|
+
this.baseTransport.write(data, formatter);
|
|
38
|
+
this.metrics.successfulRequests++;
|
|
39
|
+
this.resetFailureCount();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.metrics.failedRequests++;
|
|
43
|
+
this.recordFailure();
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
const duration = Date.now() - startTime;
|
|
47
|
+
this.updateAverageResponseTime(duration);
|
|
48
|
+
}
|
|
49
|
+
async writeAsync(data, formatter) {
|
|
50
|
+
if (!this.canWrite()) {
|
|
51
|
+
throw new Error('Circuit breaker is open');
|
|
52
|
+
}
|
|
53
|
+
this.metrics.totalRequests++;
|
|
54
|
+
const startTime = Date.now();
|
|
55
|
+
try {
|
|
56
|
+
if (this.baseTransport.writeAsync) {
|
|
57
|
+
await this.baseTransport.writeAsync(data, formatter);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
this.baseTransport.write(data, formatter);
|
|
61
|
+
}
|
|
62
|
+
this.metrics.successfulRequests++;
|
|
63
|
+
this.resetFailureCount();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
this.metrics.failedRequests++;
|
|
67
|
+
this.recordFailure();
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
const duration = Date.now() - startTime;
|
|
71
|
+
this.updateAverageResponseTime(duration);
|
|
72
|
+
}
|
|
73
|
+
canWrite() {
|
|
74
|
+
const threshold = this.options.threshold || 5;
|
|
75
|
+
const timeout = this.options.timeout || 60000;
|
|
76
|
+
if (this.state.state === 'closed') {
|
|
77
|
+
const timeSinceOpen = Date.now() - this.state.lastFailureTime;
|
|
78
|
+
if (timeSinceOpen >= timeout) {
|
|
79
|
+
this.setState('open');
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (this.state.failureCount >= threshold) {
|
|
85
|
+
this.setState('closed');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (this.state.state === 'half-open' && this.state.lastFailureTime > 0 && Date.now() - this.state.lastFailureTime < timeout) {
|
|
89
|
+
this.setState('open');
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return this.state.state === 'open' || this.state.state === 'half-open';
|
|
93
|
+
}
|
|
94
|
+
setState(newState) {
|
|
95
|
+
const oldState = this.state.state;
|
|
96
|
+
if (oldState !== newState) {
|
|
97
|
+
this.state.state = newState;
|
|
98
|
+
this.options.onStateChange?.(oldState, newState);
|
|
99
|
+
if (newState === 'closed') {
|
|
100
|
+
this.options.onTrip?.(this.state.failureCount);
|
|
101
|
+
}
|
|
102
|
+
else if (newState === 'open' && oldState === 'closed') {
|
|
103
|
+
this.options.onReset?.();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
resetFailureCount() {
|
|
108
|
+
if (this.state.failureCount === 0)
|
|
109
|
+
return;
|
|
110
|
+
this.state.failureCount = Math.max(0, Math.floor(this.state.failureCount * 0.9));
|
|
111
|
+
}
|
|
112
|
+
updateAverageResponseTime(duration) {
|
|
113
|
+
if (this.metrics.totalRequests > 0) {
|
|
114
|
+
// Use exponential moving average: newAvg = oldAvg * 0.9 + duration * 0.1
|
|
115
|
+
this.metrics.averageResponseTime =
|
|
116
|
+
this.metrics.averageResponseTime * 0.9 + duration * 0.1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
recordFailure() {
|
|
120
|
+
this.state.failureCount++;
|
|
121
|
+
this.state.lastFailureTime = Date.now();
|
|
122
|
+
if (this.state.failureCount === 1) {
|
|
123
|
+
this.state.state = 'half-open';
|
|
124
|
+
}
|
|
125
|
+
if (this.state.failureCount >= (this.options.threshold || 5)) {
|
|
126
|
+
this.setState('closed');
|
|
127
|
+
if (this.options.resetTimeout) {
|
|
128
|
+
this.resetTimer = setTimeout(() => {
|
|
129
|
+
this.reset();
|
|
130
|
+
}, this.options.resetTimeout);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
getMetrics() {
|
|
135
|
+
return {
|
|
136
|
+
...this.metrics,
|
|
137
|
+
currentState: this.state.state
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
reset() {
|
|
141
|
+
this.state = {
|
|
142
|
+
failureCount: 0,
|
|
143
|
+
lastFailureTime: 0,
|
|
144
|
+
state: 'open'
|
|
145
|
+
};
|
|
146
|
+
this.metrics = {
|
|
147
|
+
totalRequests: 0,
|
|
148
|
+
failedRequests: 0,
|
|
149
|
+
successfulRequests: 0,
|
|
150
|
+
currentState: 'open',
|
|
151
|
+
averageResponseTime: 0
|
|
152
|
+
};
|
|
153
|
+
if (this.resetTimer) {
|
|
154
|
+
clearTimeout(this.resetTimer);
|
|
155
|
+
this.resetTimer = undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
destroy() {
|
|
159
|
+
if (this.resetTimer) {
|
|
160
|
+
clearTimeout(this.resetTimer);
|
|
161
|
+
this.resetTimer = undefined;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
isAsyncSupported() {
|
|
165
|
+
return this.baseTransport.isAsyncSupported?.() || false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Transport } from "../transports/Transport.js";
|
|
2
|
+
import { TransportConfig } from "../types/index.js";
|
|
3
|
+
import { LogData } from "../types/index.js";
|
|
4
|
+
import { Formatter } from "../core/Formatter.js";
|
|
5
|
+
export interface DeadLetterQueueOptions {
|
|
6
|
+
transport: TransportConfig;
|
|
7
|
+
maxRetries?: number;
|
|
8
|
+
retryableErrorCodes?: string[];
|
|
9
|
+
deadLetterFile?: string;
|
|
10
|
+
onDeadLetter?: (deadLetter: DeadLetterLog) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface DeadLetterLog extends LogData {
|
|
13
|
+
deadLetterReason: string;
|
|
14
|
+
originalError?: string;
|
|
15
|
+
retryCount: number;
|
|
16
|
+
failedAt: Date;
|
|
17
|
+
}
|
|
18
|
+
export declare class DeadLetterQueue implements Transport {
|
|
19
|
+
private transport;
|
|
20
|
+
private maxRetries;
|
|
21
|
+
private retryableErrorCodes;
|
|
22
|
+
private deadLetterFile?;
|
|
23
|
+
private onDeadLetter?;
|
|
24
|
+
private deadLetters;
|
|
25
|
+
constructor(options: DeadLetterQueueOptions);
|
|
26
|
+
write(data: LogData, formatter: Formatter): Promise<void>;
|
|
27
|
+
writeAsync(data: LogData, formatter: Formatter): Promise<void>;
|
|
28
|
+
private writeWithRetry;
|
|
29
|
+
private writeDeadLetterToFile;
|
|
30
|
+
getDeadLetters(): DeadLetterLog[];
|
|
31
|
+
clearDeadLetters(): void;
|
|
32
|
+
destroy(): Promise<void>;
|
|
33
|
+
isAsyncSupported(): boolean;
|
|
34
|
+
}
|