Skip to content

Commit

Permalink
feat(routes): add normalized strings to context
Browse files Browse the repository at this point in the history
  • Loading branch information
dnlup committed Feb 21, 2022
1 parent 17b9f1b commit 4b172d6
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 44 deletions.
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ It supports Fastify versions `>=3.0.0`.
- [`sendGaugeMetric(name, value)`](#sendgaugemetricname-value)
- [`sendSetMetric(name, value)`](#sendsetmetricname-value)
- [Hooks](#hooks)
- [Request and Reply context](#request-and-reply-context)
- [API](#api)
- [Configuration `options`](#configuration-options)
- [Routes labels generation modes](#routes-labels-generation-modes)
Expand Down Expand Up @@ -88,7 +89,9 @@ fastify.register(require('@immobiliarelabs/fastify-metrics'), {
const route = {
// This is required in order to associate a metric to a route
config: {
routeId: 'root.getStatus',
metrics: {
routeId: 'root.getStatus',
},
},
url: '/',
method: 'GET',
Expand All @@ -103,9 +106,12 @@ fastify.listen(3000);

### Notes

The plugin uses the key `routeId` in the `config` object of the `Reply.context`. If none is found a default `noId` string is used.
The plugin uses the key `routeId` in the `metrics` object of the `config` object in the `Request.context` or `Reply.context`. If none is found a default `noId` string is used.

See https://github.com/fastify/fastify/blob/master/docs/Routes.md#config.
See

- https://github.com/fastify/fastify/blob/master/docs/Routes.md#config
- [request and reply context](#request-and-reply-context)

## Metrics collected

Expand Down Expand Up @@ -208,6 +214,16 @@ The plugin uses the following hooks:
- `onResponse`: to measure response time
- `onError`: to count errors

## Request and Reply context

The plugin adds a `metrics` object to the context for convenience with the following properties:

- `routeId` <`string`>: the id for the current route
- `fastifyPrefix` <`string`>: the prefix of the fastify instance registering the route, with the `/` replaced with `.` and without the `.` at the beginning.
- `routesPrefix` <`string`>: the routes prefix passed to the plugin options and without `.` at the beginning and end.

These properties can be useful when using a custom [`getLabel`](routes-labels-generation-modes) function.

## API

This module exports a [plugin registration function](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md#register).
Expand Down Expand Up @@ -267,7 +283,7 @@ The `getLabel` function in this mode will have the following signature:

- `prefix` <`string`> The plugin routes prefix value.
- `options` [<`Object`>](https://www.fastify.io/docs/latest/Reference/Hooks/#onroute) The route registration options.
- **Returns:** <`string`> The route label.
- **Returns:** <`string`> The route label string without any `.` at the beginning or end.

##### `dynamic`

Expand All @@ -283,7 +299,7 @@ The `getLabel` function in this mode will have the following signature:

- `request`
- `reply`
- **Returns:** <`string`> The route label.
- **Returns:** <`string`> The route label string without any `.` at the beginning or end.

The `this` context of the function is bound to the fastify instance of the request.

Expand Down
6 changes: 5 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ declare module 'fastify' {
}

interface FastifyReply {
metricsLabel?: string;
sendTimingMetric: typeof Client.prototype.timing;
sendCounterMetric: typeof Client.prototype.counter;
sendGaugeMetric: (name: string, value: number) => void;
Expand All @@ -85,7 +84,12 @@ declare module 'fastify' {

interface FastifyContextConfig {
metrics: {
/** The id for this route that will be used for the label */
routeId: string;
/** The fastify prefix for this route */
fastifyPrefix: string;
/** The prefix the pugin should add for the registered routes */
routesPrefix: string;
};
}
}
24 changes: 17 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { hrtime2ns, hrtime2ms, hrtime2s } = require('@dnlup/hrtime-utils');
const hooks = require('./lib/hooks');
const staticMode = require('./lib/routes/static');
const dynamicMode = require('./lib/routes/dynamic');
const { normalizeFastifyPrefix, normalizeRoutePrefix } = require('./lib/util');

const STATSD_METHODS = [
'counter',
Expand Down Expand Up @@ -182,18 +183,29 @@ module.exports = fp(
metricsConfig.routes.errors ||
metricsConfig.routes.hits
) {
fastify.addHook('onRoute', (options) => {
// TODO: check for duplicates when registering routes
options.config = options.config || {};
options.config.metrics = options.config.metrics || {};
options.config.metrics.fastifyPrefix = normalizeFastifyPrefix(
options.prefix
);
options.config.metrics.routesPrefix =
normalizeRoutePrefix(prefix);
});
if (mode === 'dynamic') {
let getLabel =
metricsConfig.routes.getLabel || dynamicMode.getLabel;
if (typeof getLabel !== 'function') {
throw new Error('"getLabel" must be a function.');
}
fastify.decorateRequest('metricsLabel', '');
fastify.decorateReply('metricsLabel', '');
fastify.decorateRequest('_metricsLabel', '');
fastify.decorateReply('_metricsLabel', '');
fastify.addHook('onRequest', function (request, reply, next) {
// TODO: skip noId routes here and in hooks.
const label = getLabel.call(this, request, reply);
request.metricsLabel = label;
reply.metricsLabel = label;
request._metricsLabel = label;
reply._metricsLabel = label;
next();
});
} else {
Expand All @@ -202,10 +214,8 @@ module.exports = fp(
if (typeof getLabel !== 'function') {
throw new Error('"getLabel" must be a function.');
}
// TODO: skip noId routes here and in hooks.
fastify.addHook('onRoute', (options) => {
// TODO: check for duplicates when registering routes
options.config = options.config || {};
options.config.metrics = options.config.metrics || {};
options.config.metrics.label = getLabel(prefix, options);
});
}
Expand Down
7 changes: 5 additions & 2 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ fastify.after((err) => {

fastify.get('/', async function (request, reply) {
expectType<FastifyContextConfig>(request.context.config);
expectType<{ routeId: string }>(request.context.config.metrics);
expectType<{
routeId: string;
fastifyPrefix: string;
routesPrefix: string;
}>(request.context.config.metrics);
expectType<string>(request.context.config.metrics.routeId);
expectType<string | undefined>(request.metricsLabel);
expectType<typeof Client.prototype.timing>(request.sendTimingMetric);
Expand All @@ -50,7 +54,6 @@ fastify.after((err) => {
(name: string, value: number) => void
>(request.sendSetMetric);

expectType<string | undefined>(reply.metricsLabel);
expectType<typeof Client.prototype.timing>(reply.sendTimingMetric);
expectType<typeof Client.prototype.counter>(reply.sendCounterMetric);
expectType<
Expand Down
22 changes: 11 additions & 11 deletions lib/routes/dynamic.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
'use strict';

const {
getRouteId,
normalizeFastifyPrefix,
normalizeRoutePrefix,
} = require('../util');
const { getRouteId } = require('../util');

exports.bindTimingMetric = function (client) {
return function (name, value, sampling) {
client.timing(`${this.metricsLabel}.${name}`, value, sampling);
client.timing(`${this._metricsLabel}.${name}`, value, sampling);
};
};

exports.bindCounterMetric = function (client) {
return function (name, value, sampling) {
client.counter(`${this.metricsLabel}.${name}`, value, sampling);
client.counter(`${this._metricsLabel}.${name}`, value, sampling);
};
};

exports.bindGaugeMetric = function (client) {
return function (name, value) {
client.gauge(`${this.metricsLabel}.${name}`, value);
client.gauge(`${this._metricsLabel}.${name}`, value);
};
};

exports.bindSetMetric = function (client) {
return function (name, value) {
client.set(`${this.metricsLabel}.${name}`, value);
client.set(`${this._metricsLabel}.${name}`, value);
};
};

exports.getLabel = function (request) {
const id = getRouteId(request.context.config);
const fastifyPrefix = normalizeFastifyPrefix(this.prefix);
const routePrefix = normalizeRoutePrefix(this.metricsRoutesPrefix);
const fastifyPrefix = request.context.config.metrics.fastifyPrefix
? `${request.context.config.metrics.fastifyPrefix}.`
: request.context.config.metrics.fastifyPrefix;
const routePrefix = request.context.config.metrics.routesPrefix
? `${request.context.config.metrics.routesPrefix}.`
: request.context.config.metrics.routesPrefix;
return `${fastifyPrefix}${routePrefix}${id}`;
};
14 changes: 7 additions & 7 deletions lib/routes/static.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
'use strict';

const {
getRouteId,
normalizeRoutePrefix,
normalizeFastifyPrefix,
} = require('../util');
const { getRouteId } = require('../util');

exports.bindTimingMetric = function (client) {
return function sendTimingMetric(name, value, sampling) {
Expand Down Expand Up @@ -39,7 +35,11 @@ exports.bindSetMetric = function (client) {

exports.getLabel = function (prefix, options) {
const id = getRouteId(options.config);
const fastifyPrefix = normalizeFastifyPrefix(options.prefix);
const routePrefix = normalizeRoutePrefix(prefix);
const fastifyPrefix = options.config.metrics.fastifyPrefix
? `${options.config.metrics.fastifyPrefix}.`
: options.config.metrics.fastifyPrefix;
const routePrefix = options.config.metrics.routesPrefix
? `${options.config.metrics.routesPrefix}.`
: options.config.metrics.routesPrefix;
return `${fastifyPrefix}${routePrefix}${id}`;
};
25 changes: 15 additions & 10 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ exports.getRouteId = function (config) {
};

exports.normalizeFastifyPrefix = function (prefix) {
let normalized = prefix.replace(/\//g, '.');
if (normalized) {
// remove `.` at the beginning and add trailing `.`
normalized = `${normalized.slice(1)}.`;
if (prefix) {
return prefix.replace(/\//g, '.').slice(1);
}
return normalized;
return prefix;
// let normalized = prefix.replace(/\//g, '.');
// if (normalized) {
// // remove `.` at the beginning and add trailing `.`
// normalized = `${normalized.slice(1)}.`;
// }
// return normalized;
};

exports.normalizeRoutePrefix = function (prefix) {
let routePrefix = prefix;
if (routePrefix) {
// add trailing `.`
routePrefix = `${routePrefix}.`;
}
// let routePrefix = prefix;
// if (routePrefix) {
// // add trailing `.`
// routePrefix = `${routePrefix}.`;
// }
let routePrefix = prefix.trim();
return routePrefix;
};
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4b172d6

Please sign in to comment.