Skip to content

Commit

Permalink
feat: add errorFilter option to bypass incrementing failure stats
Browse files Browse the repository at this point in the history
Refs: #262
  • Loading branch information
lance committed Mar 5, 2019
1 parent b0d4488 commit 8018012
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 0 deletions.
6 changes: 6 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ const defaults = {
* can open. This is similar to `options.allowWarmUp` in that no matter how many
* failures there are, if the number of requests within the statistical window
* does not exceed this threshold, the circuit will remain closed. Default: 0
* @param options.errorFilter {Function} an optional function that will be
* called when the circuit's function fails (returns a rejected Promise). If
* this function returns truthy, the circuit's failure statistics will not be
* incremented. This is useful, for example, when you don't want HTTP 404 to
* trip the circuit, but still want to handle it as a failure case.
* @return a {@link CircuitBreaker} instance
*/
function circuitBreaker (action, options) {
Expand Down
8 changes: 8 additions & 0 deletions lib/circuit.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ Please use options.errorThresholdPercentage`;
* can open. This is similar to `options.allowWarmUp` in that no matter how many
* failures there are, if the number of requests within the statistical window
* does not exceed this threshold, the circuit will remain closed. Default: 0
* @param options.errorFilter {Function} an optional function that will be
* called when the circuit's function fails (returns a rejected Promise). If
* this function returns truthy, the circuit's failure statistics will not be
* incremented. This is useful, for example, when you don't want HTTP 404 to
* trip the circuit, but still want to handle it as a failure case.
*/
class CircuitBreaker extends EventEmitter {
constructor (action, options) {
Expand All @@ -82,6 +87,7 @@ class CircuitBreaker extends EventEmitter {
this.options.rollingPercentilesEnabled =
options.rollingPercentilesEnabled !== false;
this.options.capacity = Number.isInteger(options.capacity) ? options.capacity : Number.MAX_SAFE_INTEGER;
this.options.errorFilter = options.errorFilter || (_ => false);

this.semaphore = new Semaphore(this.options.capacity);

Expand Down Expand Up @@ -541,6 +547,8 @@ function fallback (circuit, err, args) {
}

function fail (circuit, err, args, latency) {
if (circuit.options.errorFilter(err)) return;

/**
* Emitted when the circuit breaker action fails
* @event CircuitBreaker#failure
Expand Down
60 changes: 60 additions & 0 deletions test/error-filter-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';

const test = require('tape');
const opossum = require('../');

function mightFail (errorCode) {
const err = new Error('Something went wrong');
err.statusCode = errorCode;
return Promise.reject(err);
}

const options = {
errorThresholdPercentage: 1,
resetTimeout: 10000,
// if this function returns true, the error statistics
// should not be incremented
errorFilter: err => err.statusCode < 500
};

test('Bypasses failure stats if errorFilter returns true', t => {
t.plan(3);

const breaker = opossum(mightFail, options);
breaker.fire(400)
.then(t.fail)
.catch(err => {
t.equal(err.statusCode, 400);
t.equal(breaker.stats.failures, 0);
t.ok(breaker.closed);
t.end();
});
});

test('Increments failure stats if errorFilter returns false', t => {
t.plan(3);

const breaker = opossum(mightFail, options);
breaker.fire(500)
.then(t.fail)
.catch(err => {
t.equal(err.statusCode, 500);
t.equal(breaker.stats.failures, 1);
t.ok(breaker.open);
t.end();
});
});

test('Increments failure stats if no filter is provided', t => {
t.plan(3);
const breaker = opossum(mightFail, { errorThresholdPercentage: 1 });
breaker.fire(500)
.then(t.fail)
.catch(err => {
t.equal(err.statusCode, 500);
console.log(breaker.stats);
t.equal(breaker.stats.failures, 1);
t.ok(breaker.open);
t.end();
});
});

0 comments on commit 8018012

Please sign in to comment.