From 6efb0a540546bd78d0ea582e949beb3c77be3288 Mon Sep 17 00:00:00 2001 From: Ivan Santos Date: Sat, 25 Sep 2021 22:29:52 -0500 Subject: [PATCH] feat: add defaultLabels support --- .../opentelemetry-options.interface.ts | 5 ++- src/middleware/api-metrics.middleware.ts | 12 +++++-- .../middleware/api-metrics.middleware.spec.ts | 35 +++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/interfaces/opentelemetry-options.interface.ts b/src/interfaces/opentelemetry-options.interface.ts index 4e024d8..5eef432 100644 --- a/src/interfaces/opentelemetry-options.interface.ts +++ b/src/interfaces/opentelemetry-options.interface.ts @@ -1,4 +1,5 @@ import { ModuleMetadata, Type, Abstract } from '@nestjs/common'; +import { Labels } from '@opentelemetry/api-metrics'; export type OpenTelemetryModuleOptions = { /** @@ -50,8 +51,6 @@ export type OpenTelemetryMetrics = { timeBuckets?: number[], requestSizeBuckets?: number[], responseSizeBuckets?: number[], - }, - defaultLabels?: { - [key: string]: string | number + defaultLabels?: Labels, }, }; diff --git a/src/middleware/api-metrics.middleware.ts b/src/middleware/api-metrics.middleware.ts index 3560584..7acbd18 100644 --- a/src/middleware/api-metrics.middleware.ts +++ b/src/middleware/api-metrics.middleware.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, NestMiddleware } from '@nestjs/common'; import * as responseTime from 'response-time'; import * as urlParser from 'url'; -import { Counter, ValueRecorder } from '@opentelemetry/api-metrics'; +import { Counter, Labels, ValueRecorder } from '@opentelemetry/api-metrics'; import { OpenTelemetryModuleOptions } from '../interfaces'; import { MetricService } from '../metrics/metric.service'; import { OPENTELEMETRY_MODULE_OPTIONS } from '../opentelemetry.constants'; @@ -43,6 +43,8 @@ export class ApiMetricsMiddleware implements NestMiddleware { private responseSizeValueRecorder: ValueRecorder; + private defaultLabels: Labels; + constructor( @Inject(OPENTELEMETRY_MODULE_OPTIONS) private readonly options: OpenTelemetryModuleOptions = {}, @Inject(MetricService) private readonly metricService: MetricService, @@ -76,9 +78,11 @@ export class ApiMetricsMiddleware implements NestMiddleware { }); const { - timeBuckets = [], requestSizeBuckets = [], responseSizeBuckets = [], + timeBuckets = [], requestSizeBuckets = [], responseSizeBuckets = [], defaultLabels = {}, } = options?.metrics?.apiMetrics; + this.defaultLabels = defaultLabels; + this.requestDuration = this.metricService.getValueRecorder('http_request_duration_seconds', { boundaries: timeBuckets.length > 0 ? timeBuckets : this.defaultLongRunningRequestBuckets, description: 'HTTP latency value recorder in seconds', @@ -125,7 +129,9 @@ export class ApiMetricsMiddleware implements NestMiddleware { const responseLength: number = parseInt(res.get('Content-Length'), 10) || 0; const status = res.statusCode || 500; - const labels = { method, status, path }; + const labels: Labels = { + method, status, path, ...this.defaultLabels, + }; this.requestSizeValueRecorder.bind(labels).record(requestLength); this.responseSizeValueRecorder.bind(labels).record(responseLength); diff --git a/tests/e2e/middleware/api-metrics.middleware.spec.ts b/tests/e2e/middleware/api-metrics.middleware.spec.ts index 68b5177..3e4ce57 100644 --- a/tests/e2e/middleware/api-metrics.middleware.spec.ts +++ b/tests/e2e/middleware/api-metrics.middleware.spec.ts @@ -235,6 +235,41 @@ describe('Api Metrics Middleware', () => { expect(/http_server_error_total 1/.test(text)).toBeTruthy(); }); + it('registers requests with custom labels', async () => { + const testingModule = await Test.createTestingModule({ + imports: [OpenTelemetryModule.forRoot({ + metrics: { + apiMetrics: { + enable: true, + defaultLabels: { + custom: 'label', + }, + }, + }, + })], + controllers: [AppController], + }).compile(); + testingModule.useLogger(new EmptyLogger()); + + app = testingModule.createNestApplication(); + await app.init(); + + const agent = request(app.getHttpServer()); + await agent.get('/example/internal-error'); + + // Workaround for delay of metrics going to prometheus + await new Promise(resolve => setTimeout(resolve, 200)); + + // TODO: OpenTelemetry exporter does not expose server in a public function. + // @ts-ignore + // eslint-disable-next-line no-underscore-dangle + const { text } = await request(exporter._server) + .get('/metrics') + .expect(200); + + expect(/custom/.test(text)).toBeTruthy(); + }); + afterEach(async () => { metrics.disable(); if (exporter) {