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.
Files changed (56) hide show
  1. package/README.md +102 -359
  2. package/dist/cjs/aggregation/LogAggregator.js +60 -31
  3. package/dist/cjs/core/Formatter.js +3 -4
  4. package/dist/cjs/core/Logger.js +41 -24
  5. package/dist/cjs/filters/Filter.js +52 -18
  6. package/dist/cjs/filters/index.js +0 -1
  7. package/dist/cjs/index.js +6 -1
  8. package/dist/cjs/transports/CircuitBreakerTransport.js +171 -0
  9. package/dist/cjs/transports/DeadLetterQueue.js +129 -0
  10. package/dist/cjs/transports/FileTransport.js +78 -7
  11. package/dist/cjs/transports/HttpTransport.js +15 -3
  12. package/dist/cjs/transports/RetryTransport.js +199 -0
  13. package/dist/cjs/transports/index.js +3 -0
  14. package/dist/cjs/types/TypeInterfaces.js +2 -0
  15. package/dist/cjs/utils/index.js +78 -0
  16. package/dist/esm/aggregation/LogAggregator.d.ts +8 -29
  17. package/dist/esm/aggregation/LogAggregator.js +60 -31
  18. package/dist/esm/core/Formatter.js +1 -2
  19. package/dist/esm/core/Logger.d.ts +13 -3
  20. package/dist/esm/core/Logger.js +40 -23
  21. package/dist/esm/filters/Filter.d.ts +24 -22
  22. package/dist/esm/filters/Filter.js +47 -17
  23. package/dist/esm/filters/index.d.ts +0 -1
  24. package/dist/esm/filters/index.js +0 -1
  25. package/dist/esm/index.d.ts +3 -2
  26. package/dist/esm/index.js +6 -3
  27. package/dist/esm/transports/CircuitBreakerTransport.d.ts +43 -0
  28. package/dist/esm/transports/CircuitBreakerTransport.js +167 -0
  29. package/dist/esm/transports/DeadLetterQueue.d.ts +34 -0
  30. package/dist/esm/transports/DeadLetterQueue.js +92 -0
  31. package/dist/esm/transports/FileTransport.d.ts +4 -0
  32. package/dist/esm/transports/FileTransport.js +78 -7
  33. package/dist/esm/transports/HttpTransport.d.ts +2 -0
  34. package/dist/esm/transports/HttpTransport.js +15 -3
  35. package/dist/esm/transports/RetryTransport.d.ts +67 -0
  36. package/dist/esm/transports/RetryTransport.js +195 -0
  37. package/dist/esm/transports/Transport.d.ts +1 -0
  38. package/dist/esm/transports/index.d.ts +3 -0
  39. package/dist/esm/transports/index.js +3 -0
  40. package/dist/esm/types/TypeInterfaces.d.ts +7 -0
  41. package/dist/esm/types/TypeInterfaces.js +1 -0
  42. package/dist/esm/utils/index.d.ts +15 -0
  43. package/dist/esm/utils/index.js +72 -0
  44. package/package.json +87 -71
  45. package/dist/cjs/filters/SpecificFilters.js +0 -71
  46. package/dist/cjs/utils/ColorUtil.js +0 -42
  47. package/dist/cjs/utils/TimeUtil.js +0 -26
  48. package/dist/cjs/utils/Timerutil.js +0 -22
  49. package/dist/esm/filters/SpecificFilters.d.ts +0 -41
  50. package/dist/esm/filters/SpecificFilters.js +0 -64
  51. package/dist/esm/utils/ColorUtil.d.ts +0 -4
  52. package/dist/esm/utils/ColorUtil.js +0 -38
  53. package/dist/esm/utils/TimeUtil.d.ts +0 -3
  54. package/dist/esm/utils/TimeUtil.js +0 -22
  55. package/dist/esm/utils/Timerutil.d.ts +0 -8
  56. package/dist/esm/utils/Timerutil.js +0 -18
@@ -1,5 +1,4 @@
1
- import { TimeUtil } from "../utils/TimeUtil.js";
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, any>;
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): any;
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
  }
@@ -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
- export class Logger {
5
+ import { Timer } from "../utils/index.js";
6
+ import { EventEmitter } from "events";
7
+ export class Logger extends EventEmitter {
5
8
  constructor(options = {}) {
6
- this.transports = [];
7
- this.filters = [];
8
- this.aggregators = [];
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(); // Set enrichers, default to new instance
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
- initializedTransports.push(transportConfig);
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
- logData = this.enrichers.process(logData);
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
- try {
311
- const result = aggregator.flush();
312
- if (result instanceof Promise) {
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
+ }
@@ -1,2 +1 @@
1
1
  export * from './Filter.js';
2
- export * from './SpecificFilters.js';
@@ -1,2 +1 @@
1
1
  export * from './Filter.js';
2
- export * from './SpecificFilters.js';
@@ -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
- export { Logger, ConsoleTransport, FileTransport, HttpTransport, FilterableTransport, CompositeFilter, OrFilter, NotFilter, PredicateFilter, LevelFilter, PrefixFilter, MetadataFilter, FieldFilter, BatchAggregator, TimeBasedAggregator, CompositeAggregator, MetadataEnricher, LogEnrichmentPipeline };
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
+ }