Skip to content

Commit

Permalink
fix: adding .metrics property and circuit breaker metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
leftieFriele committed Nov 4, 2024
1 parent 6f14c0a commit 99ccee8
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 3 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ For a complete list of options, consult the [undici documentation](https://undic

Closes the client and it's connections.

## Metrics

The expose metrics on the circuit breaking behaviour.

| name | type | description |
|------------------------------|---------|-------------------------------------------------------------------------------------------|
| `http_client_breaker_events` | counter | Counters on events exposed by the circuit breaker library,<br/>see [opossum] for details |


[@metrics/metric]: https://github.com/metrics-js/metric '@metrics/metric'
[abslog]: https://github.com/trygve-lie/abslog 'abslog'
[undici]: https://undici.nodejs.org/
Expand Down
32 changes: 32 additions & 0 deletions lib/http-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Agent, request, interceptors } from 'undici';
import createError from 'http-errors';
import Opossum from 'opossum';
import abslog from 'abslog';
import Metrics from '@metrics/client';

/**
* @typedef HttpClientOptions
Expand All @@ -22,10 +23,12 @@ export default class HttpClient {
#abortController;
#agent;
#breaker;
#breakerCounter;
#followRedirects;
// eslint-disable-next-line no-unused-private-class-members
#hasFallback = false;
#logger;
#metrics = new Metrics();
#throwOn400;
#throwOn500;

Expand Down Expand Up @@ -66,6 +69,27 @@ export default class HttpClient {
timeout,
});

this.#breakerCounter = this.#metrics.counter({
name: 'http_client_breaker_events',
description: 'Metrics on breaker events',
});
for (const eventName of this.#breaker.eventNames()) {
//@ts-ignore
const event = eventName;
//@ts-ignore
this.#breaker.on(event, (e) => {
let obj = Array.isArray(e) ? e[0] : e;
this.#breakerCounter.inc({
labels: {
//@ts-ignore
name: event,
...(obj && obj.path && { path: obj.path }),
...(obj && obj.origin && { origin: obj.origin }),
},
});
});
}

this.#agent = new Agent({
keepAliveMaxTimeout,
keepAliveTimeout,
Expand All @@ -84,6 +108,14 @@ export default class HttpClient {
}
}

/**
*
* @returns {import('@metrics/client')}
*/
get metrics() {
return this.#metrics;
}

async #request(options = {}) {
if (this.#followRedirects || options.redirectable) {
const { redirect } = interceptors;
Expand Down
70 changes: 67 additions & 3 deletions tests/http-client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,71 @@ await test('http-client - circuit breaker behaviour', async (t) => {
assert.strictEqual(response.statusCode, 200);
await afterEach(client);
});
// await t.test('has a .metrics property', async () => {
// const client = new HttpClient({ threshold: 50, reset: breakerReset });
// });
});

await test('http-client: metrics', async (t) => {
await t.test('has a .metrics property', () => {
const client = new HttpClient();
assert.notEqual(client.metrics, undefined);
});
await t.test('metric on open breakers', async () => {
beforeEach();
const url = 'http://lochalost:3003';
const client = new HttpClient({
threshold: 1,
reset: 10,
throwOn400: false,
throwOn500: false,
});
const metrics = [];
//@ts-expect-error
client.metrics.on('data', (metric) => {
metrics.push(metric);
});
//@ts-expect-error
client.metrics.on('end', () => {
assert.strictEqual(metrics.length, 6);
assert.strictEqual(metrics[0].name, 'http_client_breaker_events');
assert.strictEqual(metrics[0].type, 2);
assert.strictEqual(metrics[0].labels[0].value, 'fire');
assert.strictEqual(metrics[0].labels[1].value, '/not-found');

assert.strictEqual(metrics[1].name, 'http_client_breaker_events');
assert.strictEqual(metrics[1].type, 2);
assert.strictEqual(metrics[1].labels[0].value, 'failure');

assert.strictEqual(metrics[2].name, 'http_client_breaker_events');
assert.strictEqual(metrics[2].type, 2);
assert.strictEqual(metrics[2].labels[0].value, 'open');

assert.strictEqual(metrics[3].name, 'http_client_breaker_events');
assert.strictEqual(metrics[3].type, 2);
assert.strictEqual(metrics[3].labels[0].value, 'fire');

assert.strictEqual(metrics[4].name, 'http_client_breaker_events');
assert.strictEqual(metrics[4].type, 2);
assert.strictEqual(metrics[4].labels[0].value, 'close');
});
try {
// Make the circuit open
await client.request({
path: '/not-found',
origin: url,
method: 'GET',
});
// eslint-disable-next-line no-unused-vars
} catch (_) {
//
}
await wait(11);
// Wait for circuit to reset, before using the client again.
await client.request({
path: '/',
origin: 'http://localhost:3003',
method: 'GET',
});
//@ts-expect-error
client.metrics.push(null);
await afterEach(client);
});
});

0 comments on commit 99ccee8

Please sign in to comment.