Code Monkey home page Code Monkey logo

nestjs-otel's Introduction

Nest Logo

NestJS OpenTelemetry (OTEL)

Build Status NPM

Description

OpenTelemetry module for Nest.

Questions

For questions and support please use the official Discord channel.

Why

Setting up observability metrics with nestjs requires multiple libraries and patterns. OpenTelemetry has support for multiple exporters and types of metrics such as Prometheus Metrics.

Observability

Please read this comprehensive whitepaper if that's your first time working with metrics, tracing, and logs.

observability-signals

Installation

npm i nestjs-otel @opentelemetry/sdk-node --save

Setup

Some peers dependencies are required when some configurations are enabled.

@opentelemetry/exporter-prometheus
  1. Create tracing file (tracing.ts):
import {
  CompositePropagator,
  W3CTraceContextPropagator,
  W3CBaggagePropagator,
} from '@opentelemetry/core';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { JaegerPropagator } from '@opentelemetry/propagator-jaeger';
import { B3InjectEncoding, B3Propagator } from '@opentelemetry/propagator-b3';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
import * as process from 'process';

const otelSDK = new NodeSDK({
  metricReader: new PrometheusExporter({
    port: 8081,
  }),
  spanProcessor: new BatchSpanProcessor(new JaegerExporter()),
  contextManager: new AsyncLocalStorageContextManager(),
  textMapPropagator: new CompositePropagator({
    propagators: [
      new JaegerPropagator(),
      new W3CTraceContextPropagator(),
      new W3CBaggagePropagator(),
      new B3Propagator(),
      new B3Propagator({
        injectEncoding: B3InjectEncoding.MULTI_HEADER,
      }),
    ],
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

export default otelSDK;

// You can also use the shutdown method to gracefully shut down the SDK before process shutdown
// or on some operating system signal.
process.on('SIGTERM', () => {
  otelSDK
    .shutdown()
    .then(
      () => console.log('SDK shut down successfully'),
      err => console.log('Error shutting down SDK', err)
    )
    .finally(() => process.exit(0));
});
  1. Import the metric file and start otel node SDK:
import otelSDK from './tracing';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger } from 'nestjs-pino';

async function bootstrap() {
  // Start SDK before nestjs factory create
  await otelSDK.start();

  const app = await NestFactory.create(AppModule);
  app.useLogger(app.get(Logger));
  await app.listen(3000);
}
bootstrap();
  1. Configure nest-otel:
const OpenTelemetryModuleConfig = OpenTelemetryModule.forRoot({
  metrics: {
    hostMetrics: true, // Includes Host Metrics
    apiMetrics: {
      enable: true, // Includes api metrics
      defaultAttributes: {
        // You can set default labels for api metrics
        custom: 'label',
      },
      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
    },
  },
});

@Module({
  imports: [OpenTelemetryModuleConfig],
})
export class AppModule {}

Span Decorator

If you need, you can define a custom Tracing Span for a method. It works async or sync. Span takes its name from the parameter; but by default, it is the same as the method's name

import { Span } from 'nestjs-otel';

@Span('CRITICAL_SECTION')
async getBooks() {
    return [`Harry Potter and the Philosopher's Stone`];
}

Tracing Service

In case you need to access native span methods for special logics in the method block:

import { TraceService } from 'nestjs-otel';

@Injectable()
export class BookService {
  constructor(private readonly traceService: TraceService) {}

  @Span()
  async getBooks() {
    const currentSpan = this.traceService.getSpan(); // --> retrives current span, comes from http or @Span
    await this.doSomething();
    currentSpan.addEvent('event 1');
    currentSpan.end(); // current span end

    const span = this.traceService.startSpan('sub_span'); // start new span
    span.setAttributes({ userId: 1 });
    await this.doSomethingElse();
    span.end(); // new span ends
    return [`Harry Potter and the Philosopher's Stone`];
  }
}

Metric Service

OpenTelemetry Metrics allow a user to collect data and export it to metrics backend like Prometheus.

import { MetricService } from 'nestjs-otel';
import { Counter } from '@opentelemetry/api';

@Injectable()
export class BookService {
  private customMetricCounter: Counter;

  constructor(private readonly metricService: MetricService) {
    this.customMetricCounter = this.metricService.getCounter('custom_counter', {
      description: 'Description for counter',
    });
  }

  async getBooks() {
    this.customMetricCounter.add(1);
    return [`Harry Potter and the Philosopher's Stone`];
  }
}

Metric Decorators

Metric Class Instances

If you want to count how many instance of a specific class has been created:

@OtelInstanceCounter() // It will generate a counter called: app_MyClass_instances_total.
export class MyClass {}

Metric Class Method

If you want to increment a counter on each call of a specific method:

@Injectable()
export class MyService {
  @OtelMethodCounter()
  doSomething() {}
}
@Controller()
export class AppController {
  @Get()
  @OtelMethodCounter() // It will generate `app_AppController_doSomething_calls_total` counter.
  doSomething() {
    // do your stuff
  }
}

Metric Param Decorator

You have the following decorators:

  • @OtelCounter()
  • @OtelUpDownCounter()
  • @OtelHistogram()
  • @OtelObservableGauge()
  • @OtelObservableCounter()
  • @OtelObservableUpDownCounter()

Example of usage:

import { OtelCounter } from 'nestjs-otel';
import { Counter } from '@opentelemetry/api';

@Controller()
export class AppController {
  @Get('/home')
  home(
    @OtelCounter('app_counter_1_inc', { description: 'counter 1 description' }) counter1: Counter
  ) {
    counter1.add(1);
  }
}

API Metrics with Middleware

Impl Otel Metric Prometheus Metric Description Metric Type
โœ… http.server.request.count http_server_request_count_total Total number of HTTP requests. Counter
โœ… http.server.response.count http_server_response_count_total Total number of HTTP responses. Counter
โœ… http.server.abort.count http_server_abort_count_total Total number of data transfers aborted. Counter
โœ… http.server.duration http_server_duration The duration of the inbound HTTP request. Histogram
โœ… http.server.request.size http_server_request_size Size of incoming bytes. Histogram
โœ… http.server.response.size http_server_response_size Size of outgoing bytes. Histogram
โœ… http.server.response.success.count http_server_response_success_count_total Total number of all successful responses. Counter
โœ… http.server.response.error.count http_server_response_error_count_total Total number of server error responses. Counter
โœ… http.client.request.error.count http_client_request_error_count_total Total number of client error requests. Counter

Prometheus Metrics

When metricExporter is defined in otel SDK with a PrometheusExporterit will start a new process on port 8081 (default port) and metrics will be available at http://localhost:8081/metrics.

Using with a logger

Pino with instrumentation

This approach uses otel instrumentation to automatically inject spanId and traceId.

import { PinoInstrumentation } from '@opentelemetry/instrumentation-pino';

const otelSDK = new NodeSDK({
  instrumentations: [new PinoInstrumentation()],
});

Pino with custom formatter

This approach uses the global trace context for injecting SpanId and traceId as a property of your structured log.

import Pino, { Logger } from 'pino';
import { LoggerOptions } from 'pino';
import { trace, context } from '@opentelemetry/api';

export const loggerOptions: LoggerOptions = {
  formatters: {
    log(object) {
      const span = trace.getSpan(context.active());
      if (!span) return { ...object };
      const { spanId, traceId } = trace.getSpan(context.active())?.spanContext();
      return { ...object, spanId, traceId };
    },
  },
};

export const logger: Logger = Pino(loggerOptions);

Examples

A full working examples are available. This includes a nestjs application fully integrated with prometheus, grafana, loki and tempo:

Stargazers over time

Stargazers over time

nestjs-otel's People

Contributors

dependabot[bot] avatar eroo36 avatar github-actions[bot] avatar killercup avatar mentos1386 avatar mertalev avatar myduckisgoingmad avatar npdev453 avatar patrickspies avatar petrzjunior avatar pragmaticivan avatar sobuno avatar vomba avatar yannick-bonnefond avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nestjs-otel's Issues

TraceService returning the wrong span

Hi,

I'm a little confused with the documentation of the interaction between the @Span decorator and the TraceService. From what I've understood, TraceService.getSpan function returns the latest opened span of the currently active context. This means that, with the following snippet, the log should output TestService.myHelper.

export class TestService {
  constructor(private readonly traceService: TraceService) {}

  @Span()
  myHelper() {
    const span = this.traceService.getSpan();
    console.log((span as any).name); // Not a ReadableSpan, hack for test purpose
  }
}

My issue here is that it does not work as expected:

  1. In the context of an HTTP request, the span returned has GET /test as name, i.e. the span wrapping the TestService.myHelper span. Please note that the @Span decorator works as expected because the span is correctly opened and closed, the behavior mismatch seems to be related to the TraceService.
  2. In a raw context, for instance calling the snippet above from a onModuleInit() function in any class, the span returned by the TraceService is undefined. Which seems legitimate because the (1) point indicate that the service does not return the span opened by @Span.
Controller's code
@Controller()
export class TestController {
  constructor(private readonly svc: TestService) {}

  @Get('test')
  handle(): void {
    this.svc.myHelper();
  }
}
Module's code
@Module({
  imports: [
    OpenTelemetryModule.forRoot({
      metrics: {
        hostMetrics: true,
        defaultMetrics: true,
        apiMetrics: {
          enable: true,
        },
      },
    }),
    CommonModule,
  ],
  providers: [TestService],
  controllers: [TestController],
})
export class RootModule {
  constructor(private readonly svc: TestService) {}

  onModuleInit() {
    this.svc.myHelper();
  }
}

My question here is: is that the behavior expected? From the documentation in the README, it seems that it's a bug. I've provided the minimal code to reproduce, its fairly simple to reproduce :)

Thanks!

Readme not up to date

Looks like the suggested tracing.ts mentioned in the Readme is not up to date.

HttpTraceContextPropagator needs to be W3CTraceContextPropagator
HttpBaggagePropagator needs to be W3CBaggagePropagator

import { BatchSpanProcessor } from '@opentelemetry/tracing'; needs to be import {BatchSpanProcessor} from '@opentelemetry/sdk-trace-base';

Context Propagation in Microservices.

Hey, great library, it realIy makes it easier to bootstrap things up. Feel this is a how to and should be a Stack overflow question.

I wanted to check if there is something I can use to propagate context from one MS to another. I am using the Nestjs transports for this. I am always manually add traceIDs to messages and use them as parent traceIDs in subsequent transactions. Is there a more idiomatic/dependency injection/clean way to do this using the constructs in the library? I am okay to contribute to the library as I will be using this for all my services.

Thanks again for the great library.

nestjs-otel v2

It's time to start tracking the v2 development. Here is a shortlist of things I am planning to do - there will be most probably more, but we need to start somewhere :] Feel free to suggest new things!

Progress

  • [ ]

Breaking Changes

  • Remove nodeSDKConfiguration param and expect NodeSDK to be created via node require instead. Otel Node SDK works best if initialized before anything for proper instrumentation.

Ideas worth exploring

Arrow function breaks class methods

Commit 12fa02c introduced the following change:

- descriptor.value = function (...args: any[]) {
+ descriptor.value = (...args: any[]) => {

The difference between function and arrow function is that arrow function modifies this keyword to be taken from definition scope. This causes problems when decorating class methods:

class MyClass {
  number = 42;

  @OtelMethodCounter()
  async method() {
    return this.number;
  }
}

method() should return 42, but when decorated, it actually returns undefined, because this no longer refers to the class instance.
I suggest that we revert back to using function.

Http auto instrumentation fails when using @FileInterceptor

Sample fork : https://github.com/benitezho/nestjs-otel

Using: nodeJS 16.1.0

Description

I uploaded a sample nestjs main module ready to run.
In the example i created a AppController that outputs on the console the active span from the current context, the line that reproduces the issue its:
// @UseInterceptors(FileInterceptor('field'))
If you uncomment this line, the active span from the current context its undefined when a new request its incoming.

My guess is that Nestjs's @FileInterceptor that uses multer under the hood, somehow it's replacing the async hook request listeners. For that reason, i don't know if this issue should be here.

SyntaxError: Unexpected token '.'

I am getting the following error:

/node_modules/nestjs-otel/lib/opentelemetry-core.module.js:74

const { apiMetrics = { enable: false }, } = this.options?.metrics;

^


SyntaxError: Unexpected token '.'

at wrapSafe (internal/modules/cjs/loader.js:1053:16)

at Module._compile (internal/modules/cjs/loader.js:1101:27)

at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)

at Module.load (internal/modules/cjs/loader.js:985:32)

at Function.Module._load (internal/modules/cjs/loader.js:878:14)

at Module.require (internal/modules/cjs/loader.js:1025:19)

at require (internal/modules/cjs/helpers.js:72:18)

at Object.<anonymous> (/srv/viax-service-stl/node_modules/nestjs-otel/lib/opentelemetry.module.js:12:37)

at Module._compile (internal/modules/cjs/loader.js:1137:30)

at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)

With the following configuration:

import { OpenTelemetryModule } from 'nestjs-otel';
import { BatchSpanProcessor } from '@opentelemetry/tracing';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
import { CompositePropagator } from '@opentelemetry/core';
import { B3InjectEncoding, B3Propagator } from '@opentelemetry/propagator-b3';

const OpenTelemetryModuleConfig = OpenTelemetryModule.forRoot({
  metrics: {
    hostMetrics: true, // Includes Host Metrics
    defaultMetrics: true, // Includes Default Metrics
    apiMetrics: {
      enable: true, // Includes api metrics
      timeBuckets: [],
    },
  },
  nodeSDKConfiguration: {
    spanProcessor: new BatchSpanProcessor(new JaegerExporter({
      host: 'host.docker.internal', // For local testing
      port: 6832,
    })),
    contextManager: new AsyncLocalStorageContextManager(),
    textMapPropagator: new CompositePropagator({
      propagators: [
        new B3Propagator(),
        new B3Propagator({
          injectEncoding: B3InjectEncoding.MULTI_HEADER,
        }),
      ],
    }),
    instrumentations: [getNodeAutoInstrumentations()],
  },
});

packaje.json

 "@opentelemetry/api": "^1.0.1",
    "@opentelemetry/auto-instrumentations-node": "^0.22.0",
    "@opentelemetry/context-async-hooks": "^0.22.0",
    "@opentelemetry/exporter-jaeger": "^0.22.0",
    "@opentelemetry/exporter-zipkin": "^0.22.0",
    "@opentelemetry/host-metrics": "^0.22.0",
    "@opentelemetry/instrumentation": "^0.22.0",
    "@opentelemetry/instrumentation-express": "^0.22.0",
    "@opentelemetry/instrumentation-graphql": "^0.22.0",
    "@opentelemetry/instrumentation-http": "^0.22.0",
    "@opentelemetry/metrics": "^0.22.0",
    "@opentelemetry/node": "^0.22.0",
    "@opentelemetry/propagator-b3": "^0.22.0",
    "@opentelemetry/tracing": "^0.22.0",

Perhaps you know what could be the problem?

apiMetrics not working (nest v8)

All the apiMetrics aren't showing up in the metrics page, only the default system ones are.

Using the basic snippet.

OpenTelemetryModule.forRoot({
      metrics: {
        hostMetrics: true, // Includes Host Metrics
        defaultMetrics: true, // Includes Default Metrics
        apiMetrics: {
          enable: true, // Includes api metrics
          // timeBuckets: [], // You can change the default time buckets
        },
        defaultLabels: {
          app: process.env.npm_package_name,
          version: process.env.npm_package_version,
        },
      },
    }),

if I only have apiMetrics enabled, I get:

# no registered metrics

as the metrics response. I have no idea how, but one in around 500 tries I saw for a second the needed metrics, but can't reproduce - so in general, it doesn't work.

otel/opentelemetry-collector-dev:latest no longer ships with extensions

otel/opentelemetry-collector-dev:latest no longer ships with extensions

collector_1     | Error: cannot load configuration: unknown extensions type "health_check" for health_check
collector_1     | 2021/11/14 11:40:03 collector server run finished with error: cannot load configuration: unknown extensions type "health_check" for health_check
collector_1 exited with code 1

https://github.com/pragmaticivan/nestjs-otel/blob/main/examples/nestjs-prom-grafana-tempo-otel-collector/docker-compose.yml#L76

The last working image I found was: otel/opentelemetry-collector-dev:20ac40ae3c535195341bdcefef07f98a1ecedcf1

You can see here: https://github.com/open-telemetry/opentelemetry-collector/blob/main/CHANGELOG.md#v0340-beta it was moved to contrib

Version 2.2.0 is not compatible with NestJS 7.x

The version 2.2.0 requires npm packages from NestJs version 8.x.

This forces the application developed using NestJS 7.x to use --legacy-peer-deps.

Could the library be updated to support both NestJS 7.x and NestJS 8.x

Problem with more then one span

I observe very strange behavior. If the application has more than one span, then information about them is not sent to the collector (I use Jaeger).

This works and I can see span in Jaeger:

  @Span('doSomething')
  async doSomething() {
    console.log('');
  }

But this code does not send any spans to Jaeger:

  @Span('doSomething')
  async doSomething() {
    console.log('doSomething');
  }

  @Span('doSomethingElse')
  async doSomethingElse() {
    console.log('doSomethingElse');
  }

I also get the same behavior if I add any nested span using TraceService or if I add any instrumentation.

I use next config:

const OpenTelemetryModuleConfig = OpenTelemetryModule.forRoot({
  metrics: {
    hostMetrics: true,
    defaultMetrics: true, 
    apiMetrics: {
      enable: true, 
      timeBuckets: [],
    },
  },
  nodeSDKConfiguration: {
    spanProcessor: new BatchSpanProcessor(new JaegerExporter({
      host: 'host.docker.internal',
      port: 6832,
    })),
    contextManager: new AsyncLocalStorageContextManager(),
    textMapPropagator: new CompositePropagator({
      propagators: [
        new B3Propagator(),
        new B3Propagator({
          injectEncoding: B3InjectEncoding.MULTI_HEADER,
        }),
      ],
    }),
    resource: new Resource({
      [ResourceAttributes.SERVICE_NAME]: 'stl',
    }),
  },
});

Package broken with newest opentelemetry dependencies

When using the latest opentelementy packages (0.27.0), the Nest application fails to start with the following error:

/home/node/app/node_modules/nestjs-otel/src/metrics/metric-data.ts:27
      const valueRecorder = meter.createValueRecorder(name, options);
                                  ^
TypeError: meter.createValueRecorder is not a function
    at getOrCreateValueRecorder (/home/node/app/node_modules/nestjs-otel/src/metrics/metric-data.ts:27:35)
    at MetricService.getOrCreateValueRecorder (/home/node/app/node_modules/nestjs-otel/src/metrics/metric.service.ts:26:36)
    at MetricService.getValueRecorder (/home/node/app/node_modules/nestjs-otel/src/metrics/metric.service.ts:20:17)
    at new ApiMetricsMiddleware (/home/node/app/node_modules/nestjs-otel/src/middleware/api-metrics.middleware.ts:90:47)
    at Injector.instantiateClass (/home/node/app/node_modules/@nestjs/core/injector/injector.js:301:19)
    at callback (/home/node/app/node_modules/@nestjs/core/injector/injector.js:48:41)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async Injector.resolveConstructorParams (/home/node/app/node_modules/@nestjs/core/injector/injector.js:124:24)
    at async Injector.loadInstance (/home/node/app/node_modules/@nestjs/core/injector/injector.js:52:9)
    at async Injector.loadMiddleware (/home/node/app/node_modules/@nestjs/core/injector/injector.js:61:9)

API metrics dynamic labels

I cannot find a way to add dynamic labels to prometheus metrics. I can see that middleware extracts some data from request (like path and status code) but would it be possible to add a callback which would return dynamic labels to middleware? I have some custom parameters from NestJS what I need to publish in Prom metrics.

Exposing metrics using exporter-colector instead of exporter-prometheus

Currently documentation has this section defined:

  withPrometheusExporter: {
    enable: true, // Enables prometheus exporter
    withHostMetrics: true, // Includes Host Metrics
    withDefaultMetrics: true, // Includes Default Metrics
    withHttpMiddleware: {  // Includes api metrics
      enable: true,
      timeBuckets: [],
    },
  },

Which makes me think that i MUST use prometheus exporter for metrics exporter and can't use something like @opentelemetry/exporter-collector instead of it.

To be clear, i would like to use @opentelemetry/exporter-collector instead of @opentelemetry/exporter-prometheus while still retaining withHostMetrics: true, withDefaultMetrics: true and withHttpmiddleware: { enable: true }.

Something as:

const OpenTelemetryModuleConfig = OpenTelemetryModule.register({
  metrics: {
    host: true, // Includes Host Metrics
    default: true, // Includes Default Metrics
    http: { // Includes api metrics
       enable: true,
       timeBuckets: [],
     }, 
   },
  nodeSDKConfiguration: {
    metricExporter: new CollectorMetricExporter(), // Define whatever exporter you wish
    spanProcessor: new BatchSpanProcessor(new JaegerExporter()),
    contextManager: new AsyncLocalStorageContextManager(),
    textMapPropagator: new CompositePropagator({
      propagators: [
        new JaegerPropagator(),
        new HttpTraceContextPropagator(),
        new HttpBaggagePropagator(),
        new B3Propagator(),
        new B3Propagator({
          injectEncoding: B3InjectEncoding.MULTI_HEADER,
        }),
      ],
    }),
    instrumentations: [getNodeAutoInstrumentations()],
  },
});

Question of the span decorator

Hi, I am confused with this code, why context.with use context.active ?

In my opinion it will cause parentSpan always be the rootSpan without context.with in the code.

const currentSpan = trace.getSpan(context.active());
const tracer = trace.getTracer('default')

return context.with(trace.setSpan(context.active(), currentSpan), () => {
  // ...
});

I think it should be

const tracer = trace.getTracer('default')
const span = tracer.startSpan(
  name || `${target.constructor.name}.${propertyKey}`,
);

return context.with(trace.setSpan(context.active(), span), () => {
  // ...
});

For example

class Test {
  @Span()
  f1() {
    // ...
  }

  @Span()
  f2() {
    this.f1();
  }
}

current Span decorator won't let f1 be the child of f2

Thanks for your help.

Track http requests

Track http api calls, for example whenever I make api calls with axios from the server.

Nestjs-otel Api middleware >2.3.0 gives error with Fastify Adapter

With NestJs 7.6.18 (uses fastify 3.14.0) and nestjs-otel 2.4.0 we get the following error in our otherwise working app.
We are in the process of upgrading to NestJS 8 and will report if the problem persists.

To reproduce

Navigate to any route in the application

Side note 1

http://localhost:8081/metrics does work

Side note 2

The advertized Discord inivite link seems to be expired.

Console error message

[Nest] 85656   - 01/09/2021, 13:42:33   [ExceptionHandler] Error: TypeError: req.get is not a function
    at /Users/dirk/git/heat-server-aws/node_modules/nestjs-otel/lib/middleware/api-metrics.middleware.js:89:48
    at ServerResponse.onHeaders (/Users/dirk/git/heat-server-aws/node_modules/response-time/index.js:58:7)
    at ServerResponse.writeHead (/Users/dirk/git/heat-server-aws/node_modules/on-headers/index.js:35:16)
    at onSendEnd (/Users/dirk/git/heat-server-aws/node_modules/fastify/lib/reply.js:428:7)
    at onSendHook (/Users/dirk/git/heat-server-aws/node_modules/fastify/lib/reply.js:379:5)
    at preserializeHookEnd (/Users/dirk/git/heat-server-aws/node_modules/fastify/lib/reply.js:365:3)
    at preserializeHook (/Users/dirk/git/heat-server-aws/node_modules/fastify/lib/reply.js:345:5)
    at _Reply.Reply.send (/Users/dirk/git/heat-server-aws/node_modules/fastify/lib/reply.js:184:7)
    at FastifyAdapter.reply (/Users/dirk/git/heat-server-aws/node_modules/@nestjs/platform-fastify/adapters/fastify-adapter.js:46:29)
    at RouterResponseController.apply (/Users/dirk/git/heat-server-aws/node_modules/@nestjs/core/router/router-response-controller.js:13:36)
    at /Users/dirk/git/heat-server-aws/node_modules/@nestjs/core/router/router-execution-context.js:175:48
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async /Users/dirk/git/heat-server-aws/node_modules/@nestjs/core/router/router-execution-context.js:47:13
    at async Object.<anonymous> (/Users/dirk/git/heat-server-aws/node_modules/@nestjs/core/router/router-proxy.js:9:17) +15ms

Nestjs jaeger trace with dns lookup

Hi, I am using this library for tracing with open telemetry and jaeger exporter.
For the incoming request to the nestjs service I see in jaeger, it is reporting as DNS lookup rather than an HTTP request. Is there anything wrong with my configuration?

const otelSDK = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'nest-otel-lib',
}),
spanProcessor: new BatchSpanProcessor(new JaegerExporter()),
instrumentations: [
getNodeAutoInstrumentations(),
],
});
const bootstrap = async () => {
otelSDK.start();
const { appDispatcher } = await import('wms-bff-core');
const { AppModule } = await import('./app.module');
await appDispatcher(AppModule);
};
bootstrap();

Here is the sample of the trace in Jaeger.

image

Getting route match path instead of full route

Hello!

First of all, nice package, very useful for Prometheus. Then, I'm experimenting Prometheus and its metrics (and observability overall), so I'm pretty new.

Context

Currently, my application expose metrics based on all paths (which is something great), until I noticed that every path is exposed in metrics. For instance, a route matching /api/v1/users/:userId will creates a metric with the requested userId ({ path: "/api/v1/users/myUserIDRoot"}). Here is the configuration I have for otel:

const telemetryConfig = OpenTelemetryModule.forRoot({
  metrics: {
    defaultMetrics: true, // Includes Default Metrics
    apiMetrics: {
      enable: true, // Includes api metrics
      timeBuckets: [],
    },
  },
  nodeSDKConfiguration: {
    metricExporter: new PrometheusExporter({
      port: 3001,
    }),
  },
});

Current Behavior

With the above configuration, I have metrics like these for path:

http_request_duration_seconds_bucket{method="GET",status="200",path="/api/v1/videos/11e6f85f-02ba-4e11-ab9b-1300d3946942",le="600"} 1 1627067714334
http_request_duration_seconds_bucket{method="GET",status="200",path="/api/v1/videos/11e6f85f-02ba-4e11-ab9b-1300d3946942",le="+Inf"} 1 1627067714334

Expected behavior

http_request_duration_seconds_bucket{method="GET",status="200",path="/api/v1/videos/:videoId",le="600"} 1 1627067714334
http_request_duration_seconds_bucket{method="GET",status="200",path="/api/v1/videos/:videoId",le="+Inf"} 1 1627067714334

Problem

First, I was wondering if it was a good thing to expose every id's of resources in metrics, as it could generate a lot of entry ? Then, is there a way this module can only expose the endpoint (e.g. /api/v1/users/:userId), maybe I missed something in the configuration ?

Thanks!

Question: Installation issues

I'm trying to integrate your package into an existing Nestjs 7 app. I've basically copied and pasted the example from the readme, but it seems to fail when I attempt to get a span. I'm very new to OpenTelemetry so can't really say I have much of a conceptual model to support debugging.

The code that generates the error is from the readme example
image
and lower down
image

The currentSpan is undefined resulting in the addEvent failing

{"level":50,"time":1629306567682,"pid":111,"hostname":"a68e9c9c5b3c","req":{"id":"dcaa44df-0249-4f43-b9ad-1948dfa74b1b","method":"POST","url":"/api/v3/authenticate","remoteAddress":"::ffff:172.28.0.3","remotePort":53100},"logId":"11fd25fa-8aec-4762-a06c-f2f15c11e0c1","context":"ExceptionsHandler","trace":"TypeError: Cannot read property 'addEvent' of undefined\n    at AuthenticationService.authenticationFinalization (/usr/src/app/dist/identity/apis/authentication/authentication.service.js:71:21)\n    at /usr/src/app/node_modules/nestjs-otel/lib/tracing/decorators/span.js:14:35\n    at NoopContextManager.with (/usr/src/app/node_modules/@opentelemetry/api/build/src/context/NoopContextManager.js:36:24)\n    at ContextAPI.with (/usr/src/app/node_modules/@opentelemetry/api/build/src/api/context.js:71:54)\n    at AuthenticationService.PropertyDescriptor (/usr/src/app/node_modules/nestjs-otel/lib/tracing/decorators/span.js:11:38)\n    at AuthenticationController.authenticateWithUsernameAndPassword (/usr/src/app/dist/identity/apis/authentication/authentication.controller.js:46:64)\n    at /usr/src/app/node_modules/@nestjs/core/router/router-execution-context.js:38:29","msg":"Cannot read property 'addEvent' of undefined"}

When stepping into the traceService.getSpan I can see that _currentContext map is empty
image

Any thoughts on what may not be correct?

Many thanks for making this package and the time to review this message.

Create a CatchAll ExceptionsFilter

Register a Counter:

name: 'http_exceptions',
labels : ['method', 'status', 'path'],

Support config: withExceptionFilter

It should conditionally replace provide: APP_FILTER, if enabled.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.