Skip to content

Commit

Permalink
feat: metric prefixes
Browse files Browse the repository at this point in the history
  • Loading branch information
mertalev committed May 9, 2024
1 parent 42f84c2 commit 7b6928a
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 88 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const OpenTelemetryModuleConfig = OpenTelemetryModule.forRoot({
},
ignoreRoutes: ['/favicon.ico'], // You can ignore specific routes (See https://docs.nestjs.com/middleware#excluding-routes for options)
ignoreUndefinedRoutes: false, //Records metrics for all URLs, even undefined ones
prefix: 'my_prefix', // Add a custom prefix to all API metrics
},
},
});
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/metric-options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { MetricOptions } from '@opentelemetry/api';

export interface OtelMetricOptions extends MetricOptions {
/**
* A prefix to add to the name of the metric.
*/
prefix?: string;
}
1 change: 1 addition & 0 deletions src/interfaces/opentelemetry-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ export type OpenTelemetryMetrics = {
defaultAttributes?: Attributes;
ignoreRoutes?: (string | RouteInfo)[];
ignoreUndefinedRoutes?: boolean;
prefix?: string;
};
};
7 changes: 4 additions & 3 deletions src/metrics/decorators/common.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Counter, MetricOptions } from '@opentelemetry/api';
import { Counter } from '@opentelemetry/api';
import { copyMetadataFromFunctionToFunction } from '../../opentelemetry.utils';
import { getOrCreateCounter } from '../metric-data';
import { OtelMetricOptions } from '../../interfaces/metric-options.interface';

/**
* Create and increment a counter when a new instance is created
*
* @param originalClass
*/
export const OtelInstanceCounter =
(options?: MetricOptions) =>
(options?: OtelMetricOptions) =>
<T extends { new (...args: any[]): {} }>(originalClass: T) => {
const name = `app_${originalClass.name}_instances_total`;
const description = `app_${originalClass.name} object instances total`;
Expand All @@ -34,7 +35,7 @@ export const OtelInstanceCounter =
* Create and increment a counter when the method is called
*/
export const OtelMethodCounter =
(options?: MetricOptions) =>
(options?: OtelMetricOptions) =>
(
target: Object,
propertyKey: string | symbol,
Expand Down
18 changes: 10 additions & 8 deletions src/metrics/decorators/counter.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { createParamDecorator } from '@nestjs/common';
import { MetricOptions } from '@opentelemetry/api';
import { getOrCreateCounter, MetricType } from '../metric-data';
import { getOrCreateCounter } from '../metric-data';
import { OtelMetricOptions } from '../../interfaces/metric-options.interface';

export const OtelCounter = createParamDecorator((name: string, options?: MetricOptions) => {
export const OtelCounter = createParamDecorator((name: string, options?: OtelMetricOptions) => {
if (!name || name.length === 0) {
throw new Error('OtelCounter need a name argument');
}
return getOrCreateCounter(name, options);
});

export const OtelUpDownCounter = createParamDecorator((name: string, options?: MetricOptions) => {
if (!name || name.length === 0) {
throw new Error('OtelUpDownCounter need a name argument');
export const OtelUpDownCounter = createParamDecorator(
(name: string, options?: OtelMetricOptions) => {
if (!name || name.length === 0) {
throw new Error('OtelUpDownCounter need a name argument');
}
return getOrCreateCounter(name, options);
}
return getOrCreateCounter(name, options);
});
);
4 changes: 2 additions & 2 deletions src/metrics/decorators/histogram.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createParamDecorator } from '@nestjs/common';
import { MetricOptions } from '@opentelemetry/api';
import { getOrCreateHistogram } from '../metric-data';
import { OtelMetricOptions } from '../../interfaces/metric-options.interface';

export const OtelHistogram = createParamDecorator((name: string, options?: MetricOptions) => {
export const OtelHistogram = createParamDecorator((name: string, options?: OtelMetricOptions) => {
if (!name || name.length === 0) {
throw new Error('OtelHistogram need a name argument');
}
Expand Down
18 changes: 10 additions & 8 deletions src/metrics/decorators/observable.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { createParamDecorator } from '@nestjs/common';
import { MetricOptions } from '@opentelemetry/api';
import {
getOrCreateObservableCounter,
getOrCreateObservableGauge,
getOrCreateObservableUpDownCounter,
} from '../metric-data';
import { OtelMetricOptions } from '../../interfaces/metric-options.interface';

export const OtelObservableGauge = createParamDecorator((name: string, options?: MetricOptions) => {
if (!name || name.length === 0) {
throw new Error('OtelObservableGauge need a name argument');
export const OtelObservableGauge = createParamDecorator(
(name: string, options?: OtelMetricOptions) => {
if (!name || name.length === 0) {
throw new Error('OtelObservableGauge need a name argument');
}
return getOrCreateObservableGauge(name, options);
}
return getOrCreateObservableGauge(name, options);
});
);

export const OtelObservableCounter = createParamDecorator(
(name: string, options?: MetricOptions) => {
(name: string, options?: OtelMetricOptions) => {
if (!name || name.length === 0) {
throw new Error('OtelObservableCounter need a name argument');
}
Expand All @@ -23,7 +25,7 @@ export const OtelObservableCounter = createParamDecorator(
);

export const OtelObservableUpDownCounter = createParamDecorator(
(name: string, options?: MetricOptions) => {
(name: string, options?: OtelMetricOptions) => {
if (!name || name.length === 0) {
throw new Error('OtelObservableUpDownCounter need a name argument');
}
Expand Down
87 changes: 29 additions & 58 deletions src/metrics/metric-data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Counter,
MetricOptions,
UpDownCounter,
Histogram,
ObservableGauge,
Expand All @@ -9,6 +8,7 @@ import {
metrics,
} from '@opentelemetry/api';
import { OTEL_METER_NAME } from '../opentelemetry.constants';
import { OtelMetricOptions } from '../interfaces/metric-options.interface';

export type GenericMetric =
| Counter
Expand All @@ -29,82 +29,53 @@ export enum MetricType {

export const meterData: Map<string, GenericMetric> = new Map();

export function getOrCreateHistogram(name: string, options: MetricOptions = {}): Histogram {
if (meterData.has(name)) {
return meterData.get(name) as Histogram;
function getOrCreate(
name: string,
options: OtelMetricOptions = {},
type: MetricType
): GenericMetric | undefined {
const nameWithPrefix = options.prefix ? `${options.prefix}.${name}` : name;
let metric = meterData.get(nameWithPrefix);
if (metric === undefined) {
const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
metric = meter[`create${type}`](nameWithPrefix, options);
meterData.set(nameWithPrefix, metric);
}

const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
const histogram = meter.createHistogram(name, options);
meterData.set(name, histogram);
return histogram;
return metric;
}

export function getOrCreateCounter(name: string, options: MetricOptions = {}): Counter {
if (meterData.has(name)) {
return meterData.get(name) as Counter;
}

const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);

const counter = meter.createCounter(name, options);
meterData.set(name, counter);
return counter;
export function getOrCreateHistogram(name: string, options: OtelMetricOptions = {}): Histogram {
return getOrCreate(name, options, MetricType.Histogram) as Histogram;
}

export function getOrCreateUpDownCounter(name: string, options: MetricOptions = {}): UpDownCounter {
if (meterData.has(name)) {
return meterData.get(name) as UpDownCounter;
}

const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);
export function getOrCreateCounter(name: string, options: OtelMetricOptions = {}): Counter {
return getOrCreate(name, options, MetricType.Counter) as Counter;
}

const upDownCounter = meter.createUpDownCounter(name, options);
meterData.set(name, upDownCounter);
return upDownCounter;
export function getOrCreateUpDownCounter(
name: string,
options: OtelMetricOptions = {}
): UpDownCounter {
return getOrCreate(name, options, MetricType.UpDownCounter) as UpDownCounter;
}

export function getOrCreateObservableGauge(
name: string,
options: MetricOptions = {}
options: OtelMetricOptions = {}
): ObservableGauge {
if (meterData.has(name)) {
return meterData.get(name) as ObservableGauge;
}

const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);

const observableGauge = meter.createObservableGauge(name, options);
meterData.set(name, observableGauge);
return observableGauge;
return getOrCreate(name, options, MetricType.ObservableGauge) as ObservableGauge;
}

export function getOrCreateObservableCounter(
name: string,
options: MetricOptions = {}
options: OtelMetricOptions = {}
): ObservableCounter {
if (meterData.has(name)) {
return meterData.get(name) as ObservableCounter;
}

const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);

const observableCounter = meter.createObservableCounter(name, options);
meterData.set(name, observableCounter);
return observableCounter;
return getOrCreate(name, options, MetricType.ObservableCounter) as ObservableCounter;
}

export function getOrCreateObservableUpDownCounter(
name: string,
options: MetricOptions = {}
options: OtelMetricOptions = {}
): ObservableUpDownCounter {
if (meterData.has(name)) {
return meterData.get(name) as ObservableUpDownCounter;
}

const meter = metrics.getMeterProvider().getMeter(OTEL_METER_NAME);

const observableCounter = meter.createObservableCounter(name, options);
meterData.set(name, observableCounter);
return observableCounter;
return getOrCreate(name, options, MetricType.ObservableUpDownCounter) as ObservableUpDownCounter;
}
14 changes: 7 additions & 7 deletions src/metrics/metric.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Injectable } from '@nestjs/common';
import { MetricOptions } from '@opentelemetry/api';
import {
getOrCreateCounter,
getOrCreateHistogram,
Expand All @@ -8,30 +7,31 @@ import {
getOrCreateObservableUpDownCounter,
getOrCreateUpDownCounter,
} from './metric-data';
import { OtelMetricOptions } from '../interfaces/metric-options.interface';

@Injectable()
export class MetricService {
getCounter(name: string, options?: MetricOptions) {
getCounter(name: string, options?: OtelMetricOptions) {
return getOrCreateCounter(name, options);
}

getUpDownCounter(name: string, options?: MetricOptions) {
getUpDownCounter(name: string, options?: OtelMetricOptions) {
return getOrCreateUpDownCounter(name, options);
}

getHistogram(name: string, options?: MetricOptions) {
getHistogram(name: string, options?: OtelMetricOptions) {
return getOrCreateHistogram(name, options);
}

getObservableCounter(name: string, options?: MetricOptions) {
getObservableCounter(name: string, options?: OtelMetricOptions) {
return getOrCreateObservableCounter(name, options);
}

getObservableGauge(name: string, options?: MetricOptions) {
getObservableGauge(name: string, options?: OtelMetricOptions) {
return getOrCreateObservableGauge(name, options);
}

getObservableUpDownCounter(name: string, options?: MetricOptions) {
getObservableUpDownCounter(name: string, options?: OtelMetricOptions) {
return getOrCreateObservableUpDownCounter(name, options);
}
}
16 changes: 14 additions & 2 deletions src/middleware/api-metrics.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ export class ApiMetricsMiddleware implements NestMiddleware {
@Inject(MetricService) private readonly metricService: MetricService,
@Inject(OPENTELEMETRY_MODULE_OPTIONS) private readonly options: OpenTelemetryModuleOptions = {}
) {
const { defaultAttributes = {}, ignoreUndefinedRoutes = false } =
options?.metrics?.apiMetrics ?? {};
const {
defaultAttributes = {},
ignoreUndefinedRoutes = false,
prefix,
} = options?.metrics?.apiMetrics ?? {};

this.defaultAttributes = defaultAttributes;
this.ignoreUndefinedRoutes = ignoreUndefinedRoutes;
Expand All @@ -44,31 +47,37 @@ export class ApiMetricsMiddleware implements NestMiddleware {
this.httpServerRequestCount = this.metricService.getCounter('http.server.request.count', {
description: 'Total number of HTTP requests',
unit: 'requests',
prefix,
});

this.httpServerResponseCount = this.metricService.getCounter('http.server.response.count', {
description: 'Total number of HTTP responses',
unit: 'responses',
prefix,
});

this.httpServerAbortCount = this.metricService.getCounter('http.server.abort.count', {
description: 'Total number of data transfers aborted',
unit: 'requests',
prefix,
});

this.httpServerDuration = this.metricService.getHistogram('http.server.duration', {
description: 'The duration of the inbound HTTP request',
unit: 'ms',
prefix,
});

this.httpServerRequestSize = this.metricService.getHistogram('http.server.request.size', {
description: 'Size of incoming bytes',
unit: 'By',
prefix,
});

this.httpServerResponseSize = this.metricService.getHistogram('http.server.response.size', {
description: 'Size of outgoing bytes',
unit: 'By',
prefix,
});

// Helpers
Expand All @@ -77,20 +86,23 @@ export class ApiMetricsMiddleware implements NestMiddleware {
{
description: 'Total number of all successful responses',
unit: 'responses',
prefix,
}
);

this.httpServerResponseErrorCount = this.metricService.getCounter(
'http.server.response.error.count',
{
description: 'Total number of all response errors',
prefix,
}
);

this.httpClientRequestErrorCount = this.metricService.getCounter(
'http.client.request.error.count',
{
description: 'Total number of client error requests',
prefix,
}
);
}
Expand Down
Loading

0 comments on commit 7b6928a

Please sign in to comment.