From fabf6d71e52873db67b7093df5d80005083cd010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dino=20Muharemagi=C4=87?= Date: Tue, 19 Dec 2023 15:01:11 +0100 Subject: [PATCH] feat: support regular expressions in routeBlacklist values (#98) * feat: support route blacklist values using regexp * test: support route blacklist values using regexp * docs(readme): update routeBlacklist type * docs: regenerate with api-extractor --- README.md | 2 +- .../fastify-metrics.iroutemetricsconfig.md | 2 +- ...rics.iroutemetricsconfig.routeblacklist.md | 2 +- etc/fastify-metrics.api.md | 14 +++- src/__tests__/route-metrics.spec.ts | 83 +++++++++++++++++++ src/fastify-metrics.ts | 10 ++- src/types.ts | 2 +- 7 files changed, 105 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 75b0734..761e3be 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ See for details [docs](docs/api/fastify-metrics.imetricspluginoptions.md) | [overrides?](./docs/fastify-metrics.iroutemetricsconfig.overrides.md) | [IRouteMetricsOverrides](./docs/fastify-metrics.iroutemetricsoverrides.md) | | | [registeredRoutesOnly?](./docs/fastify-metrics.iroutemetricsconfig.registeredroutesonly.md) | boolean | `true` | | [customLabels?](./fastify-metrics.iroutemetricsconfig.customlabels.md) | Record<string, string \| ((request: FastifyRequest, reply: FastifyReply) => string)> | `undefined` | -| [routeBlacklist?](./docs/fastify-metrics.iroutemetricsconfig.routeblacklist.md) | readonly string\[\] | `[]` | +| [routeBlacklist?](./docs/fastify-metrics.iroutemetricsconfig.routeblacklist.md) | readonly (string \| RegExp)\[\] | `[]` | #### Route metrics enabled diff --git a/docs/api/fastify-metrics.iroutemetricsconfig.md b/docs/api/fastify-metrics.iroutemetricsconfig.md index 34fbb57..7b19413 100644 --- a/docs/api/fastify-metrics.iroutemetricsconfig.md +++ b/docs/api/fastify-metrics.iroutemetricsconfig.md @@ -23,4 +23,4 @@ export interface IRouteMetricsConfig | [methodBlacklist?](./fastify-metrics.iroutemetricsconfig.methodblacklist.md) | | readonly HTTPMethods\[\] | (Optional) A list of HTTP methods that will be excluded from metrics collection | | [overrides?](./fastify-metrics.iroutemetricsconfig.overrides.md) | | [IRouteMetricsOverrides](./fastify-metrics.iroutemetricsoverrides.md) | (Optional) Metric configuration overrides | | [registeredRoutesOnly?](./fastify-metrics.iroutemetricsconfig.registeredroutesonly.md) | | boolean | (Optional) Collect metrics only for registered routes. If false, then metrics for unknown routes /unknown-unregistered-route will be collected as well. | -| [routeBlacklist?](./fastify-metrics.iroutemetricsconfig.routeblacklist.md) | | readonly string\[\] | (Optional) A list of routes that will be excluded from metrics collection. | +| [routeBlacklist?](./fastify-metrics.iroutemetricsconfig.routeblacklist.md) | | readonly (string \| RegExp)\[\] | (Optional) A list of routes that will be excluded from metrics collection. | diff --git a/docs/api/fastify-metrics.iroutemetricsconfig.routeblacklist.md b/docs/api/fastify-metrics.iroutemetricsconfig.routeblacklist.md index 03cd5a0..1cf9b16 100644 --- a/docs/api/fastify-metrics.iroutemetricsconfig.routeblacklist.md +++ b/docs/api/fastify-metrics.iroutemetricsconfig.routeblacklist.md @@ -9,5 +9,5 @@ A list of routes that will be excluded from metrics collection. Signature: ```typescript -routeBlacklist?: readonly string[]; +routeBlacklist?: readonly (string | RegExp)[]; ``` diff --git a/etc/fastify-metrics.api.md b/etc/fastify-metrics.api.md index 7a824b8..947c6d2 100644 --- a/etc/fastify-metrics.api.md +++ b/etc/fastify-metrics.api.md @@ -5,6 +5,7 @@ ```ts import client from 'prom-client'; import { DefaultMetricsCollectorConfiguration } from 'prom-client'; +import { FastifyBaseLogger } from 'fastify'; import { FastifyPluginAsync } from 'fastify'; import { FastifyReply } from 'fastify'; import { FastifyRequest } from 'fastify'; @@ -19,7 +20,8 @@ import { SummaryConfiguration } from 'prom-client'; const _default: FastifyPluginAsync< Partial, RawServerDefault, - FastifyTypeProviderDefault + FastifyTypeProviderDefault, + FastifyBaseLogger >; export default _default; @@ -72,13 +74,19 @@ export interface IRouteMetricsConfig { string, string | ((request: FastifyRequest, reply: FastifyReply) => string) >; - enabled?: boolean; + // (undocumented) + enabled?: + | boolean + | { + histogram?: boolean; + summary?: boolean; + }; groupStatusCodes?: boolean; invalidRouteGroup?: string; methodBlacklist?: readonly HTTPMethods[]; overrides?: IRouteMetricsOverrides; registeredRoutesOnly?: boolean; - routeBlacklist?: readonly string[]; + routeBlacklist?: readonly (string | RegExp)[]; } // @public diff --git a/src/__tests__/route-metrics.spec.ts b/src/__tests__/route-metrics.spec.ts index f090e5c..e14ddae 100644 --- a/src/__tests__/route-metrics.spec.ts +++ b/src/__tests__/route-metrics.spec.ts @@ -451,6 +451,89 @@ describe('route metrics', () => { }); }); + describe(`{ routeBlacklist = [/^\\/api\\/documentation(\\/|$)/] }`, () => { + let app = fastify(); + + afterEach(async () => { + await app.close(); + }); + + beforeEach(async () => { + app = fastify(); + + await app.register(fastifyPlugin, { + endpoint: '/metrics', + routeMetrics: { + enabled: true, + routeBlacklist: [/^\/api\/documentation(\/|$)/], + }, + }); + app.get('/api/documentation', async () => { + return 'Base documentation'; + }); + app.get('/api/documentation/json', async () => { + return 'JSON documentation'; + }); + app.get('/api/documentation/yaml', async () => { + return 'YAML documentation'; + }); + app.get('/api/other', async () => { + return 'Other API endpoint'; + }); + await app.ready(); + }); + + test('metrics for regex matched routes in blacklist not exposed', async () => { + await expect( + app.inject({ + method: 'GET', + url: '/api/documentation', + }) + ).resolves.toBeDefined(); + + await expect( + app.inject({ + method: 'GET', + url: '/api/documentation/json', + }) + ).resolves.toBeDefined(); + + await expect( + app.inject({ + method: 'GET', + url: '/api/documentation/yaml', + }) + ).resolves.toBeDefined(); + + await expect( + app.inject({ + method: 'GET', + url: '/api/other', + }) + ).resolves.toBeDefined(); + + const metrics = await app.inject({ + method: 'GET', + url: '/metrics', + }); + + expect(typeof metrics.payload).toBe('string'); + + const lines = metrics.payload.split('\n'); + + expect(lines).toEqual( + expect.not.arrayContaining([ + expect.stringContaining('route="/api/documentation"'), + expect.stringContaining('route="/api/documentation/json"'), + expect.stringContaining('route="/api/documentation/yaml"'), + ]) + ); + expect(lines).toEqual( + expect.arrayContaining([expect.stringContaining('route="/api/other"')]) + ); + }); + }); + describe(`{ methodBlacklist = ['GET'] }`, () => { let app = fastify(); diff --git a/src/fastify-metrics.ts b/src/fastify-metrics.ts index 0a1f032..69f5d57 100644 --- a/src/fastify-metrics.ts +++ b/src/fastify-metrics.ts @@ -141,9 +141,13 @@ export class FastifyMetrics implements IFastifyMetrics { // routeOptions.routePath; // the URL of the route without the prefix // routeOptions.prefix; - if ( - this.options.routeMetrics.routeBlacklist?.includes(routeOptions.url) - ) { + const isRouteBlacklisted = this.options.routeMetrics.routeBlacklist?.some( + (pattern) => + typeof pattern === 'string' + ? pattern === routeOptions.url + : pattern.test(routeOptions.url) + ); + if (isRouteBlacklisted) { return; } diff --git a/src/types.ts b/src/types.ts index 44f101b..6b3338d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -194,7 +194,7 @@ export interface IRouteMetricsConfig { * * @defaultValue `undefined` */ - routeBlacklist?: readonly string[]; + routeBlacklist?: readonly (string | RegExp)[]; /** * A list of HTTP methods that will be excluded from metrics collection