diff --git a/CHANGELOG.md b/CHANGELOG.md index 01005a1..62ff7ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Optional `enableMetricsServer` option to enable of disable the metrics server. +- Optional `enableMetricsServer` option to enable or disable the metrics server. - Exposed `getMetrics` function to get the metrics in prometheus exposition format. - Add support to instrument applications based on `Next.js` framework in Node.js environment. diff --git a/src/OpenAPM.ts b/src/OpenAPM.ts index 513bb3b..eb19a26 100644 --- a/src/OpenAPM.ts +++ b/src/OpenAPM.ts @@ -34,6 +34,10 @@ export type DefaultLabels = | 'host'; export interface OpenAPMOptions { + /** + * Enable the OpenAPM + */ + enabled?: boolean; /** * Enable the metrics server * @default true @@ -84,12 +88,13 @@ export class OpenAPM extends LevitateEvents { private simpleCache: Record = {}; private path: string; private metricsServerPort: number; + private enabled: boolean; private enableMetricsServer: boolean; readonly environment: string; readonly program: string; private defaultLabels?: Record; - private requestsCounterConfig: CounterConfiguration; - private requestDurationHistogramConfig: HistogramConfiguration; + readonly requestsCounterConfig: CounterConfiguration; + readonly requestDurationHistogramConfig: HistogramConfiguration; private requestsCounter?: Counter; private requestsDurationHistogram?: Histogram; private extractLabels?: Record; @@ -101,6 +106,7 @@ export class OpenAPM extends LevitateEvents { constructor(options?: OpenAPMOptions) { super(options); // Initializing all the options + this.enabled = options?.enabled ?? true; this.path = options?.path ?? '/metrics'; this.metricsServerPort = options?.metricsServerPort ?? 9097; this.enableMetricsServer = options?.enableMetricsServer ?? true; @@ -134,8 +140,10 @@ export class OpenAPM extends LevitateEvents { this.customPathsToMask = options?.customPathsToMask; this.excludeDefaultLabels = options?.excludeDefaultLabels; - this.initiateMetricsRoute(); - this.initiatePromClient(); + if (this.enabled) { + this.initiateMetricsRoute(); + this.initiatePromClient(); + } } private getDefaultLabels = () => { @@ -174,6 +182,9 @@ export class OpenAPM extends LevitateEvents { public shutdown = async () => { return new Promise((resolve, reject) => { + if (!this.enabled) { + resolve(undefined); + } if (this.enableMetricsServer) { console.log('Shutting down metrics server gracefully.'); } @@ -276,6 +287,9 @@ export class OpenAPM extends LevitateEvents { res: ServerResponse, time: number ) => { + if (!this.enabled) { + return; + } const sanitizedPathname = getSanitizedPath(req.originalUrl ?? '/'); // Extract labels from the request params const { pathname, labels: parsedLabelsFromPathname } = @@ -290,11 +304,7 @@ export class OpenAPM extends LevitateEvents { } // Make sure you copy baseURL in case of nested routes. - const path = req.route - ? req.route?.path !== '*' - ? req.baseUrl + req.route?.path - : pathname - : pathname; + const path = req.route ? req.baseUrl + req.route?.path : pathname; const labels: Record = { path, @@ -353,6 +363,9 @@ export class OpenAPM extends LevitateEvents { }; public instrument(moduleName: SupportedModules) { + if (!this.enabled) { + return; + } try { if (moduleName === 'express') { const express = require('express'); @@ -368,10 +381,14 @@ export class OpenAPM extends LevitateEvents { } if (moduleName === 'nextjs') { const nextServer = require('next/dist/server/next-server'); - instrumentNextjs(nextServer.default, { - counter: this.requestsCounter, - histogram: this.requestsDurationHistogram - }); + instrumentNextjs( + nextServer.default, + { + counter: this.requestsCounter, + histogram: this.requestsDurationHistogram + }, + this + ); } } catch (error) { if (Object.keys(moduleNames).includes(moduleName)) { diff --git a/src/clients/nextjs.ts b/src/clients/nextjs.ts index 4bb2b4b..1225551 100644 --- a/src/clients/nextjs.ts +++ b/src/clients/nextjs.ts @@ -1,11 +1,12 @@ import type NextNodeServer from 'next/dist/server/next-server'; +import prom, { Counter, Histogram } from 'prom-client'; import chokidar from 'chokidar'; -import type { Counter, Histogram } from 'prom-client'; import { wrap } from '../shimmer'; import { loadManifest } from 'next/dist/server/load-manifest'; import { join } from 'path'; import { getRouteRegex } from 'next/dist/shared/lib/router/utils/route-regex'; import { getRouteMatcher } from 'next/dist/shared/lib/router/utils/route-matcher'; +import OpenAPM from '../OpenAPM'; const DOT_NEXT = join(process.cwd(), '.next'); @@ -84,8 +85,8 @@ const wrappedHandler = ( handler: ReturnType, ctx: { getParameterizedRoute: (route: string) => string; - counter: Counter; - histogram: Histogram; + counter?: Counter; + histogram?: Histogram; } ) => { return async function ( @@ -104,7 +105,7 @@ const wrappedHandler = ( ); ctx.counter - .labels( + ?.labels( parsedPath !== '' ? parsedPath : '/', req.method ?? 'GET', res.statusCode?.toString() ?? '500' @@ -112,7 +113,7 @@ const wrappedHandler = ( .inc(); ctx.histogram - .labels( + ?.labels( parsedPath !== '' ? parsedPath : '/', req.method ?? 'GET', res.statusCode?.toString() ?? '500' @@ -125,12 +126,20 @@ const wrappedHandler = ( export const instrumentNextjs = ( nextServer: typeof NextNodeServer, - { counter, histogram }: { counter?: Counter; histogram?: Histogram } + { counter, histogram }: { counter?: Counter; histogram?: Histogram }, + openapm: OpenAPM ) => { - if (!counter || !histogram) { - throw new Error( - 'counter and histogram are required. Make sure you are using openmetrics mode.' - ); + const ctx = { + counter, + histogram + }; + + if (typeof ctx.counter === 'undefined') { + ctx.counter = new prom.Counter(openapm.requestsCounterConfig); + } + + if (typeof ctx.histogram === 'undefined') { + ctx.histogram = new prom.Histogram(openapm.requestDurationHistogramConfig); } PATHS_CACHE.setValue();