import { Logger, HttpStatus as status } from '@nestjs/common' import CircuitBreaker from 'opossum' export class Opossum { private static readonly logger: Logger = new Logger('Opossum') static async circuit(action: (...args: any) => Promise<any>): Promise<any> { return new Promise((resolve: (value: unknown) => void, reject: (reason?: any) => void) => { try { const options: CircuitBreaker.Options = { // timeout: 3000, // resetTimeout: 5000, // rollingCountTimeout: 3000, // errorThresholdPercentage: 30, } const breaker: CircuitBreaker = new CircuitBreaker(action, options) // const breakerState: { state: CircuitBreaker.State; status: CircuitBreaker.Stats } = breaker.toJSON() // this.logger.log('BREAKER STATE: ', breakerState.state) // this.logger.log('BREAKER STATUS: ', breakerState.status) breaker.fire().then(resolve).catch(reject) Opossum.circuitEventSuccess(breaker) Opossum.circuitEventError(breaker, resolve, reject) } catch (e: any) { reject(e) } }) } private static circuitEventSuccess(breaker: CircuitBreaker): void { breaker.on('open', () => { Opossum.logger.log('Circuit breaker is open') breaker.enable() }) breaker.on('halfOpen', (resetTimeout: number) => { Opossum.logger.log('Circuit breaker is halfOpen', JSON.stringify({ resetTimeout })) breaker.clearCache() }) breaker.on('fire', (args: unknown[]) => { Opossum.logger.log('Circuit breaker is fire', JSON.stringify({ args })) breaker.fallback(() => 'circuit success') }) breaker.on('success', (result: unknown, latencyMs: number) => { Opossum.logger.log('Circuit breaker is open', JSON.stringify({ result, latencyMs })) }) breaker.on('close', () => { Opossum.logger.log('Circuit breaker is close') }) breaker.on('shutdown', () => { Opossum.logger.log('Circuit breaker is shutdown') }) } private static circuitEventError(breaker: CircuitBreaker, resolve: (value: unknown) => void, reject: (reason?: any) => void): void { breaker.on('failure', (err: Error, latencyMs: number, args: unknown[]) => { Opossum.logger.error('Circuit breaker is failure', JSON.stringify({ err, latencyMs, args })) if (Object.values(err).length > 0) { breaker.clearCache() breaker.fallback(() => 'circuit error') } else { breaker.fallback(() => err) } }) breaker.on('fallback', (result: unknown, err: Error) => { if (Object.values(err).length > 0) { Opossum.logger.error('Circuit breaker is fallback', JSON.stringify({ result, err })) reject({ stat_code: status.BAD_GATEWAY, err_code: 'APPLICATION_UNAVAILABLE', err_message: 'Server is unavailable try again later!' }) } else { Opossum.logger.log('Circuit breaker is fallback', JSON.stringify({ result, err })) resolve(err) } }) breaker.on('semaphoreLocked', (err: Error) => { Opossum.logger.error('Circuit breaker is semaphoreLocked', JSON.stringify({ err })) }) breaker.on('timeout', (err: Error) => { Opossum.logger.error('Circuit breaker is timeout', JSON.stringify({ err })) }) breaker.on('reject', (err: Error) => { Opossum.logger.error('Circuit breaker is reject', JSON.stringify({ err })) }) breaker.on('healthCheckFailed', (err: Error) => { Opossum.logger.error('Circuit breaker is healthCheckFailed', JSON.stringify({ err })) }) } }import { Logger, HttpStatus as status } from '@nestjs/common' import CircuitBreaker from 'opossum' import { apiResponse } from '~/internals/helpers/helper.api' export class Opossum { private static readonly logger: Logger = new Logger('Opossum') static async circuit(action: (...args: any) => Promise<any>): Promise<any> { return new Promise((resolve: (value: unknown) => void, reject: (reason?: any) => void) => { const options: CircuitBreaker.Options = { timeout: 3000, resetTimeout: 5000, rollingCountTimeout: 5000, errorThresholdPercentage: 50, } const breaker: CircuitBreaker = new CircuitBreaker(action, options) breaker.fire().then(resolve).catch(reject) Opossum.circuitEventSuccess(breaker, resolve) Opossum.circuitEventError(breaker, resolve) }) } private static circuitEventSuccess(breaker: CircuitBreaker, resolve: (value: unknown) => void): void { breaker.on('success', resolve) breaker.on('open', () => { Opossum.logger.log('Circuit breaker is open') breaker.clearCache() }) breaker.on('halfOpen', (resetTimeout: number) => { Opossum.logger.log('Circuit breaker is halfOpen', JSON.stringify({ resetTimeout })) breaker.fallback(() => 'circuit success') }) breaker.on('fire', (args: unknown[]) => { Opossum.logger.log('Circuit breaker is fire', JSON.stringify({ args })) }) breaker.on('success', (result: unknown, latencyMs: number) => { Opossum.logger.log('Circuit breaker is success', JSON.stringify({ result, latencyMs })) }) breaker.on('close', () => { Opossum.logger.log('Circuit breaker is close') }) breaker.on('shutdown', () => { Opossum.logger.log('Circuit breaker is shutdown') }) } private static circuitEventError(breaker: CircuitBreaker, resolve: (value: unknown) => void): void { breaker.on('failure', (err: Error, latencyMs: number, args: unknown[]) => { Opossum.logger.error('Circuit breaker is failure', JSON.stringify({ err, latencyMs, args })) breaker.fallback(() => 'circuit error') }) breaker.on('fallback', (result: unknown, err: Error) => { if (err) { Opossum.logger.error('Circuit breaker is fallback', JSON.stringify({ result, err })) resolve( apiResponse({ stat_code: status.BAD_GATEWAY, err_code: 'APPLICATION_UNAVAILABLE', err_message: 'Server is unavailable try again later!', }), ) } else { Opossum.logger.log('Circuit breaker is fallback', JSON.stringify({ result, err })) resolve(err) } }) breaker.on('semaphoreLocked', (err: Error) => { Opossum.logger.error('Circuit breaker is semaphoreLocked', JSON.stringify({ err })) }) breaker.on('timeout', (err: Error) => { Opossum.logger.error('Circuit breaker is timeout', JSON.stringify({ err })) }) breaker.on('reject', (err: Error) => { Opossum.logger.error('Circuit breaker is reject', JSON.stringify({ err })) }) } }import { Logger, HttpStatus as status } from '@nestjs/common' import { BrokenCircuitError, CircuitBreakerPolicy, CircuitState, FailureReason, IFailureEvent, ISuccessEvent, SamplingBreaker, circuitBreaker, handleAll, } from 'cockatiel' export class Cockatiel { private static readonly logger: Logger = new Logger('Cockatiel') static circuit<T = any>(handler: () => T): Promise<any> { return new Promise((resolve: (value: unknown) => void, reject: (reason?: any) => void) => { try { const breaker: CircuitBreakerPolicy = circuitBreaker(handleAll, { halfOpenAfter: 3000, breaker: new SamplingBreaker({ threshold: 0.5, duration: 5000 }), }) if (breaker.state === CircuitState.Open) { breaker.isolate() } breaker.execute<T>(handler).then(resolve).catch(reject) Cockatiel.circuitEventSuccess(breaker) Cockatiel.circuitEventError(breaker) } catch (e: any) { if (e instanceof BrokenCircuitError) { reject({ stat_code: status.INTERNAL_SERVER_ERROR, err_message: e.message }) } else { reject({ stat_code: status.INTERNAL_SERVER_ERROR, err_message: e }) } } }) } private static circuitEventSuccess(breaker: CircuitBreakerPolicy): void { breaker.onHalfOpen((data: void) => { Cockatiel.logger.log('Circuit breaker is onHalfOpen', JSON.stringify({ data })) }) breaker.onReset((data: void) => { Cockatiel.logger.log('Circuit breaker is onReset', JSON.stringify({ data })) }) breaker.onStateChange((data: CircuitState) => { Cockatiel.logger.log('Circuit breaker is onStateChange', JSON.stringify({ data })) }) breaker.onSuccess((data: ISuccessEvent) => { Cockatiel.logger.log('Circuit breaker is onSuccess', JSON.stringify({ data })) }) } private static circuitEventError(breaker: CircuitBreakerPolicy): void { breaker.onBreak((err: FailureReason<any> | { isolated: true }) => { Cockatiel.logger.error('Circuit breaker is onBreak', JSON.stringify({ err })) }) breaker.onFailure((err: IFailureEvent) => { Cockatiel.logger.error('Circuit breaker is onFailure', JSON.stringify({ err })) }) } }