Skip to content

Commit

Permalink
Merge pull request #32 from pragmaticivan/feature/abstract-prometheus
Browse files Browse the repository at this point in the history
feat: Make metrics exporter generic
  • Loading branch information
pragmaticivan authored Jun 29, 2021
2 parents a8d2782 + 6c2426b commit 2021db0
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 84 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@ opentelemetry-node-metrics
`nodeSDKConfiguration` property accepts OpenTelemetry NodeSDK configuration.

```ts
const OpenTelemetryModuleConfig = OpenTelemetryModule.register({
withPrometheusExporter: {
enable: true, // Enables prometheus exporter
withHostMetrics: true, // Includes Host Metrics
withDefaultMetrics: true, // Includes Default Metrics
withHttpMiddleware: { // Includes api metrics
enable: true,
const OpenTelemetryModuleConfig = OpenTelemetryModule.forRoot({
metrics: {
hostMetrics: true, // Includes Host Metrics
defaultMetrics: true, // Includes Default Metrics
apiMetrics: {
enable: true, // Includes api metrics
timeBuckets: [],
},
},
nodeSDKConfiguration: {
metricExporter: new PrometheusExporter({
port: 8081,
}),
spanProcessor: new BatchSpanProcessor(new JaegerExporter()),
contextManager: new AsyncLocalStorageContextManager(),
textMapPropagator: new CompositePropagator({
Expand Down Expand Up @@ -155,7 +157,7 @@ Impl |Metric |Description| Labels | Metric Type

## Prometheus Metrics

When `withPrometheusExporter` is enabled it will start a new process on port `8081` and metrics will be available at `http://localhost:8081/metrics`.
When `nodeSDKConfiguration.metricExporter` is defined with a `PrometheusExporter`it will start a new process on port `8081` (default port) and metrics will be available at `http://localhost:8081/metrics`.

## Using with a logger

Expand Down
15 changes: 6 additions & 9 deletions src/interfaces/opentelemetry-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,21 @@ import { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
export type OpenTelemetryModuleOptions = {
nodeSDKConfiguration?: Partial<NodeSDKConfiguration>
/**
* Prometheus Exporter Setup
* OpenTelemetry Metrics Setup
*/
withPrometheusExporter?: PrometheusExporterOptions;
metrics?: OpenTelemetryMetrics
};

export type PrometheusExporterOptions = {
enable?: boolean,
withHttpMiddleware?: {
export type OpenTelemetryMetrics = {
defaultMetrics?: boolean,
hostMetrics?: boolean,
apiMetrics?: {
enable?: boolean,
timeBuckets: number[]
},
withHostMetrics?: boolean,
withDefaultMetrics?: boolean,
defaultLabels?: {
[key: string]: string | number
},
metricPath?: string
port?: number,
};

export interface OpenTelemetryOptionsFactory {
Expand Down
2 changes: 1 addition & 1 deletion src/metrics/metric.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class MetricService {
private meterData: Map<string, GenericMetric> = new Map();

constructor(@Inject(OPENTELEMETRY_METER_PROVIDER) private readonly meterProvider: MeterProvider) {
this.meter = this.meterProvider.getMeter('prometheus-metrics');
this.meter = this.meterProvider.getMeter('metrics');
}

getCounter(name: string, options?: MetricOptions) {
Expand Down
5 changes: 3 additions & 2 deletions src/middleware/api-metrics.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class ApiMetricsMiddleware implements NestMiddleware {
description: 'Total number of server error requests',
});

const { timeBuckets = [] } = this.options?.withPrometheusExporter?.withHttpMiddleware;
const { timeBuckets = [] } = this.options?.metrics?.apiMetrics;
this.requestDuration = this.metricService.getValueRecorder('http_request_duration_seconds', {
boundaries: timeBuckets.length > 0 ? timeBuckets : this.defaultLongRunningRequestBuckets,
description: 'HTTP latency value recorder in seconds',
Expand All @@ -70,7 +70,8 @@ export class ApiMetricsMiddleware implements NestMiddleware {
if (path === '/favicon.ico') {
return;
}
const metricPath = this.options?.withPrometheusExporter?.metricPath ? this.options.withPrometheusExporter.metricPath : '/metrics';
// TODO Support `ignore routes array instead`
const metricPath = '/metrics';
if (path === metricPath) {
return;
}
Expand Down
92 changes: 33 additions & 59 deletions src/opentelemetry-core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import {
OnApplicationShutdown, Provider,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { HostMetrics } from '@opentelemetry/host-metrics';
import { MeterProvider } from '@opentelemetry/metrics';
import { NodeSDK } from '@opentelemetry/sdk-node';
import * as nodeMetrics from 'opentelemetry-node-metrics';
import { metrics } from '@opentelemetry/api-metrics';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OpenTelemetryModuleOptions } from './interfaces';
import { MetricService } from './metrics/metric.service';
import { ApiMetricsMiddleware } from './middleware';
import {
OPENTELEMETRY_METER_PROVIDER,
OPENTELEMETRY_MODULE_OPTIONS,
OPENTELEMETRY_PROMETHEUS_EXPORTER, OPENTELEMETRY_SDK,
OPENTELEMETRY_SDK,
} from './opentelemetry.constants';
import { TraceService } from './tracing/trace.service';

Expand All @@ -29,8 +29,8 @@ export class OpenTelemetryCoreModule implements OnApplicationShutdown, OnApplica
private readonly moduleRef: ModuleRef,
) {}

static async register(
options: OpenTelemetryModuleOptions = { withPrometheusExporter: {} },
static async forRoot(
options: OpenTelemetryModuleOptions = { metrics: {} },
): Promise<DynamicModule> {
const openTelemetryModuleOptions = {
provide: OPENTELEMETRY_MODULE_OPTIONS,
Expand All @@ -40,28 +40,16 @@ export class OpenTelemetryCoreModule implements OnApplicationShutdown, OnApplica
const providers = [
openTelemetryModuleOptions,
await this.createNodeSDKProvider(),
this.createMeterProvider(),
TraceService,
MetricService,
];

const exports: Provider[] = [
TraceService,
MetricService,
];

const {
enable = false,
} = options?.withPrometheusExporter;

if (enable) {
providers.push(this.createPrometheusExporterProvider());
providers.push(this.createMeterProvider());
providers.push(MetricService);
exports.push(MetricService);
} else {
// Conditional Injection for factories/static methods is not available yet.
// More info here: https://github.com/nestjs/nest/issues/7306
providers.push(this.noopPrometheusExporterProvider());
}

return {
module: OpenTelemetryCoreModule,
providers,
Expand All @@ -71,18 +59,23 @@ export class OpenTelemetryCoreModule implements OnApplicationShutdown, OnApplica

configure(consumer: MiddlewareConsumer) {
const {
withHttpMiddleware = { enable: false },
} = this.options?.withPrometheusExporter;
if (withHttpMiddleware.enable === true) {
apiMetrics = { enable: false },
} = this.options?.metrics;
if (apiMetrics.enable === true) {
consumer.apply(ApiMetricsMiddleware).forRoutes('*');
}
}

async onApplicationBootstrap() {
const nodeOtelSDK = this.moduleRef.get<NodeSDK>(OPENTELEMETRY_SDK);

const meterProvider = this.moduleRef.get<MeterProvider>(OPENTELEMETRY_METER_PROVIDER);
try {
this.logger.log('NestJS OpenTelemetry starting');
await nodeOtelSDK.start();
// Start method sets a custom meter provider
// when exporter is defined. Overwrites that here.
// Possible improvements can be found here: https://github.com/open-telemetry/opentelemetry-js/issues/2307
metrics.setGlobalMeterProvider(meterProvider);
} catch (e) {
this.logger.error(e?.message);
}
Expand All @@ -101,60 +94,41 @@ export class OpenTelemetryCoreModule implements OnApplicationShutdown, OnApplica
private static async createNodeSDKProvider(): Promise<Provider> {
return {
provide: OPENTELEMETRY_SDK,
useFactory: async (options: OpenTelemetryModuleOptions, exporter: PrometheusExporter) => {
const { enable = false } = options?.withPrometheusExporter;
if (enable) {
// eslint-disable-next-line no-param-reassign
options.nodeSDKConfiguration.metricExporter = exporter;
}
return new NodeSDK(options.nodeSDKConfiguration);
useFactory: (options: OpenTelemetryModuleOptions, meterProvider: MeterProvider) => {
const sdk = new NodeSDK(
{ ...options.nodeSDKConfiguration },
);
return sdk;
},
inject: [OPENTELEMETRY_MODULE_OPTIONS, OPENTELEMETRY_PROMETHEUS_EXPORTER],
};
}

private static createPrometheusExporterProvider(): Provider {
return {
provide: OPENTELEMETRY_PROMETHEUS_EXPORTER,
useFactory: (options: OpenTelemetryModuleOptions) => {
const { port = 8081 } = options?.withPrometheusExporter;
return new PrometheusExporter({ port });
},
inject: [OPENTELEMETRY_MODULE_OPTIONS],
};
}

private static noopPrometheusExporterProvider(): Provider {
return {
provide: OPENTELEMETRY_PROMETHEUS_EXPORTER,
useFactory: () => null,
inject: [OPENTELEMETRY_MODULE_OPTIONS, OPENTELEMETRY_METER_PROVIDER],
};
}

private static createMeterProvider(): Provider {
return {
provide: OPENTELEMETRY_METER_PROVIDER,
useFactory: (exporter: PrometheusExporter, options: OpenTelemetryModuleOptions) => {
useFactory: (options: OpenTelemetryModuleOptions) => {
const {
withDefaultMetrics = false, withHostMetrics = false,
} = options?.withPrometheusExporter;
defaultMetrics = false, hostMetrics = false,
} = options?.metrics;

const meterProvider = new MeterProvider({
exporter,
interval: 1000,
exporter: options?.nodeSDKConfiguration?.metricExporter,
});

if (withDefaultMetrics) {
if (defaultMetrics) {
nodeMetrics(meterProvider);
}

if (withHostMetrics) {
const hostMetrics = new HostMetrics({ meterProvider, name: 'host-metrics' });
hostMetrics.start();
if (hostMetrics) {
const host = new HostMetrics({ meterProvider, name: 'host-metrics' });
host.start();
}

return meterProvider;
},
inject: [OPENTELEMETRY_PROMETHEUS_EXPORTER, OPENTELEMETRY_MODULE_OPTIONS],
inject: [OPENTELEMETRY_MODULE_OPTIONS],
};
}
}
1 change: 0 additions & 1 deletion src/opentelemetry.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ export enum Constants {
export const OPENTELEMETRY_SDK = 'OPEN_TELEMETRY_SDK';
export const OPENTELEMETRY_MODULE_OPTIONS = 'OpenTelemetryModuleOptions';
export const OPENTELEMETRY_MODULE_ID = 'OpenTelemetryModuleId';
export const OPENTELEMETRY_PROMETHEUS_EXPORTER = 'OpenTelemetryPrometheusExporter';
export const OPENTELEMETRY_METER_PROVIDER = 'OpenTelemetryMeterProvider';
4 changes: 2 additions & 2 deletions src/opentelemetry.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { OpenTelemetryModuleOptions } from './interfaces';

@Module({})
export class OpenTelemetryModule {
static async register(
static async forRoot(
options?: OpenTelemetryModuleOptions,
): Promise<DynamicModule> {
return {
module: OpenTelemetryModule,
imports: [await OpenTelemetryCoreModule.register(options)],
imports: [await OpenTelemetryCoreModule.forRoot(options)],
};
}
}
2 changes: 1 addition & 1 deletion tests/e2e/module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createOpenTelemetryModule } from '../utils';
describe('OpenTelemetryModule', () => {
let app: INestApplication;

describe('#register', () => {
describe('#forRoot', () => {
beforeEach(async () => {
({ app } = await createOpenTelemetryModule());
});
Expand Down
2 changes: 1 addition & 1 deletion tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function createOpenTelemetryModule(
options?: OpenTelemetryModuleOptions,
): Promise<TestHarness> {
const testingModule = await Test.createTestingModule({
imports: [OpenTelemetryModule.register(options)],
imports: [OpenTelemetryModule.forRoot(options)],
}).compile();

const app = testingModule.createNestApplication();
Expand Down

0 comments on commit 2021db0

Please sign in to comment.