{"id":13818168,"url":"https://github.com/pragmaticivan/nestjs-otel","last_synced_at":"2025-12-13T17:44:58.663Z","repository":{"id":38741310,"uuid":"380251338","full_name":"pragmaticivan/nestjs-otel","owner":"pragmaticivan","description":"OpenTelemetry (Tracing + Metrics) module for Nest framework (node.js)  🔭","archived":false,"fork":false,"pushed_at":"2025-04-11T00:08:29.000Z","size":3694,"stargazers_count":610,"open_issues_count":10,"forks_count":57,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-11T15:57:14.011Z","etag":null,"topics":["hacktoberfest","metrics","nest","nestjs","nodejs","observability","opentelemetry","prometheus","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pragmaticivan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["pragmaticivan"]}},"created_at":"2021-06-25T13:45:39.000Z","updated_at":"2025-04-11T07:58:39.000Z","dependencies_parsed_at":"2024-01-08T09:05:25.006Z","dependency_job_id":"2524af2f-986b-4b87-adf1-afa3b768a1dd","html_url":"https://github.com/pragmaticivan/nestjs-otel","commit_stats":{"total_commits":252,"total_committers":20,"mean_commits":12.6,"dds":"0.44047619047619047","last_synced_commit":"e25ab39f604933e7fabe0aa20fca6b287301b4bf"},"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmaticivan%2Fnestjs-otel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmaticivan%2Fnestjs-otel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmaticivan%2Fnestjs-otel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmaticivan%2Fnestjs-otel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pragmaticivan","download_url":"https://codeload.github.com/pragmaticivan/nestjs-otel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254078634,"owners_count":22011008,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","metrics","nest","nestjs","nodejs","observability","opentelemetry","prometheus","typescript"],"created_at":"2024-08-04T07:00:34.794Z","updated_at":"2025-12-13T17:44:58.657Z","avatar_url":"https://github.com/pragmaticivan.png","language":"TypeScript","funding_links":["https://github.com/sponsors/pragmaticivan"],"categories":["Components \u0026 Libraries"],"sub_categories":[],"readme":"![nestjs-otel](https://github.com/pragmaticivan/nestjs-otel/assets/301291/40aaad82-82f9-48e5-b204-0e3cef532813)\n\n\n# NestJS OpenTelemetry (OTEL)\n\n![Build Status](https://github.com/pragmaticivan/nestjs-otel/actions/workflows/nodejs.yml/badge.svg)\n[![NPM](https://img.shields.io/npm/v/nestjs-otel.svg)](https://www.npmjs.com/package/nestjs-otel)\n\n## Description\n\n[OpenTelemetry](https://opentelemetry.io/) module for [Nest](https://github.com/nestjs/nest).\n\n## Why\n\nSetting up observability metrics with nestjs requires multiple libraries and patterns. OpenTelemetry has support for multiple exporters and types of metrics such as Prometheus Metrics.\n\n## Observability\n\nPlease read this [comprehensive whitepaper](https://github.com/cncf/tag-observability/blob/main/whitepaper.md) if that's your first time working with metrics, tracing, and logs.\n\n![observability-signals](https://user-images.githubusercontent.com/24193764/121773601-55f86b80-cb53-11eb-8c8b-262a5aad781f.png)\n\n\n## Examples\n\nA full working examples are available. This includes a nestjs application fully integrated with prometheus, grafana, loki and tempo:\n\n- [nestjs-otel-prom-grafana-tempo](https://github.com/pragmaticivan/nestjs-otel-prom-grafana-tempo )\n\n\n## Installation\n\n```bash\nnpm i nestjs-otel @opentelemetry/sdk-node --save\n```\n\n## Setup\n\nSome peers dependencies are required when some configurations are enabled.\n\n```\n@opentelemetry/exporter-prometheus\n```\n\n1. Create tracing file (`tracing.ts`):\n\n```ts\nimport {\n  CompositePropagator,\n  W3CTraceContextPropagator,\n  W3CBaggagePropagator,\n} from '@opentelemetry/core';\nimport { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport { JaegerExporter } from '@opentelemetry/exporter-jaeger';\nimport { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';\nimport { JaegerPropagator } from '@opentelemetry/propagator-jaeger';\nimport { B3Propagator } from '@opentelemetry/propagator-b3';\nimport { PrometheusExporter } from '@opentelemetry/exporter-prometheus';\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';\nimport * as process from 'process';\n\nconst otelSDK = new NodeSDK({\n  metricReader: new PrometheusExporter({\n    port: 8081,\n  }),\n  spanProcessor: new BatchSpanProcessor(new JaegerExporter()),\n  contextManager: new AsyncLocalStorageContextManager(),\n  textMapPropagator: new CompositePropagator({\n    propagators: [\n      new JaegerPropagator(),\n      new W3CTraceContextPropagator(),\n      new W3CBaggagePropagator(),\n      new B3Propagator(),\n    ],\n  }),\n  instrumentations: [getNodeAutoInstrumentations()],\n});\n\nexport default otelSDK;\n\n// You can also use the shutdown method to gracefully shut down the SDK before process shutdown\n// or on some operating system signal.\nprocess.on('SIGTERM', () =\u003e {\n  otelSDK\n    .shutdown()\n    .then(\n      () =\u003e console.log('SDK shut down successfully'),\n      err =\u003e console.log('Error shutting down SDK', err)\n    )\n    .finally(() =\u003e process.exit(0));\n});\n```\n\n2. Import the metric file and start otel node SDK:\n\n```ts\nimport otelSDK from './tracing';\nimport { NestFactory } from '@nestjs/core';\nimport { AppModule } from './app.module';\nimport { Logger } from 'nestjs-pino';\n\nasync function bootstrap() {\n  // Start SDK before nestjs factory create\n  await otelSDK.start();\n\n  const app = await NestFactory.create(AppModule);\n  app.useLogger(app.get(Logger));\n  await app.listen(3000);\n}\nbootstrap();\n```\n\n3. Configure nest-otel:\n\n3.1. With `forRoot`:\n\n```ts\nconst OpenTelemetryModuleConfig = OpenTelemetryModule.forRoot({\n  metrics: {\n    hostMetrics: true, // Includes Host Metrics\n  },\n});\n\n@Module({\n  imports: [OpenTelemetryModuleConfig],\n})\nexport class AppModule {}\n```\n\n3.2. With `forRootAsync`:\n\n```ts\nOpenTelemetryModule.forRootAsync({\n  useClass: OtelConfigService\n});\n```\n\n```ts\nimport { Injectable, Logger } from '@nestjs/common'\nimport { ConfigService } from '@nestjs/config'\nimport { OpenTelemetryOptionsFactory, OpenTelemetryModuleOptions } from 'nestjs-otel';\n\n@Injectable()\nexport class OtelConfigService implements OpenTelemetryOptionsFactory {\n  private readonly logger = new Logger(OtelConfigService.name)\n\n  constructor(private configService: ConfigService) {}\n\n  createOpenTelemetryOptions(): Promise\u003cOpenTelemetryModuleOptions\u003e | OpenTelemetryModuleOptions {\n    const { hostMetrics } = this.configService.get('otel')\n\n    return {\n      metrics: {\n        hostMetrics: hostMetrics.enabled,\n      },\n    };\n  }\n}\n```\n\n## Span Decorator\n\nIf you need, you can define a custom Tracing Span for a method. It works async or sync.\n\nSpan optionally takes one or both of the following parameters:\n  * `name` - explicit name of the span; if omitted, it is derived as `\u003cclass-name\u003e.\u003cmethod-name\u003e`.\n  * `options` - `SpanOptions` to customize the span options.\n\nYou can also supply a function as the `options` argument. It will be called with the decorated method's arguments, so you can dynamically customize the span options.\n\n\n```ts\nimport { Span } from 'nestjs-otel';\n\nexport class BooksService {\n\n  // span.name == 'CRITICAL_SECTION'\n  @Span('CRITICAL_SECTION')\n  async getBooks() {\n      return [`Harry Potter and the Philosopher's Stone`];\n  }\n\n  // span.name == 'BooksService.getBooksAgain'\n  @Span()\n  async getBooksAgain() {\n      return [`Harry Potter and the Philosopher's Stone`];\n  }\n\n  // explicitly set span options\n  @Span('getBook', { kind: SpanKind.SERVER })\n  async getBook(id: number) {\n    // ...\n  }\n\n  // options are set dynamically based on the id parameter\n  @Span('getBook', (id) =\u003e ({ attributes: { bookId: id } }))\n  async getBookAgain(id: number) {\n    // ...\n  }\n\n  // same as above, but span name is omitted and inferred automatically\n  @Span((id) =\u003e ({ attributes: { bookId: id } }))\n  async getBookOnceMore(id: number) {\n    // ...\n  }\n\n  // Capture return value as attribute\n  // Note: Explicitly type the result to ensure type safety\n  @Span({\n    onResult: (result: string[]) =\u003e ({ attributes: { 'book.count': result.length } }),\n  })\n  async getBooks() {\n    return ['Book 1', 'Book 2'];\n  }\n}\n\n```\n\n## Traceable Decorator\n\nIf you want to trace all methods in a class, you can use the `@Traceable` decorator.\n\n```ts\nimport { Traceable } from 'nestjs-otel';\n\n@Injectable()\n@Traceable()\nexport class UsersService {\n  findAll() {\n    // This method will be automatically traced\n    return [];\n  }\n\n  findOne(id: string) {\n    // This method will also be automatically traced\n    return {};\n  }\n}\n```\n\n## Current Span Decorator\n\nYou can access the current span in your controllers using the `@CurrentSpan` decorator.\n\n\u003e **Note:** This decorator only works in Controllers, Resolvers, and Gateways where NestJS handles argument injection. It does **not** work in standard service-to-service calls.\n\n```ts\nimport { Controller, Get } from '@nestjs/common';\nimport { Span } from '@opentelemetry/api';\nimport { CurrentSpan } from 'nestjs-otel';\n\n@Controller('cats')\nexport class CatsController {\n  @Get()\n  findAll(@CurrentSpan() span: Span) {\n    if (span) {\n      span.setAttribute('custom.attribute', 'value');\n    }\n    return 'This action returns all cats';\n  }\n}\n```\n\n## Baggage Decorator\n\nYou can access the OpenTelemetry Baggage (Distributed Context) in your controllers using the `@Baggage` decorator.\n\n```ts\nimport { Controller, Get } from '@nestjs/common';\nimport { Baggage } from 'nestjs-otel';\n\n@Controller('cats')\nexport class CatsController {\n  @Get()\n  findAll(@Baggage('tenant-id') tenantId: string) {\n    console.log('Tenant ID:', tenantId);\n    return 'This action returns all cats';\n  }\n}\n```\n\n## Tracing Service\n\nIn case you need to access native span methods for special logics in the method block:\n\n```ts\nimport { TraceService } from 'nestjs-otel';\n\n@Injectable()\nexport class BookService {\n  constructor(private readonly traceService: TraceService) {}\n\n  @Span()\n  async getBooks() {\n    const currentSpan = this.traceService.getSpan(); // --\u003e retrives current span, comes from http or @Span\n    await this.doSomething();\n    currentSpan.addEvent('event 1');\n    currentSpan.end(); // current span end\n\n    const span = this.traceService.startSpan('sub_span'); // start new span\n    span.setAttributes({ userId: 1 });\n    await this.doSomethingElse();\n    span.end(); // new span ends\n    return [`Harry Potter and the Philosopher's Stone`];\n  }\n}\n```\n\n## Metric Service\n\n[OpenTelemetry Metrics](https://www.npmjs.com/package/@opentelemetry/api) allow a user to collect data and export it to metrics backend like Prometheus.\n\n```ts\nimport { MetricService } from 'nestjs-otel';\nimport { Counter } from '@opentelemetry/api';\n\n@Injectable()\nexport class BookService {\n  private customMetricCounter: Counter;\n\n  constructor(private readonly metricService: MetricService) {\n    this.customMetricCounter = this.metricService.getCounter('custom_counter', {\n      description: 'Description for counter',\n    });\n  }\n\n  async getBooks() {\n    this.customMetricCounter.add(1);\n    return [`Harry Potter and the Philosopher's Stone`];\n  }\n}\n```\n\n## Metric Decorators\n\n### Metric Class Instances\n\nIf you want to count how many instance of a specific class has been created:\n\n```ts\n@OtelInstanceCounter() // It will generate a counter called: app_MyClass_instances_total.\nexport class MyClass {}\n```\n\n### Metric Class Method\n\nIf you want to increment a counter on each call of a specific method:\n\n```ts\n@Injectable()\nexport class MyService {\n  @OtelMethodCounter()\n  doSomething() {}\n}\n@Controller()\nexport class AppController {\n  @Get()\n  @OtelMethodCounter() // It will generate `app_AppController_doSomething_calls_total` counter.\n  doSomething() {\n    // do your stuff\n  }\n}\n```\n\n### Metric Param Decorator\n\nYou have the following decorators:\n\n- `@OtelCounter()`\n- `@OtelUpDownCounter()`\n- `@OtelHistogram()`\n- `@OtelGauge()`\n- `@OtelObservableGauge()`\n- `@OtelObservableCounter()`\n- `@OtelObservableUpDownCounter()`\n\nExample of usage:\n\n```ts\nimport { OtelCounter } from 'nestjs-otel';\nimport { Counter } from '@opentelemetry/api';\n\n@Controller()\nexport class AppController {\n  @Get('/home')\n  home(\n    @OtelCounter('app_counter_1_inc', { description: 'counter 1 description' }) counter1: Counter\n  ) {\n    counter1.add(1);\n  }\n}\n```\n\n## Prometheus Metrics\n\nWhen `metricExporter` is defined in otel SDK with a `PrometheusExporter`it will start a new process on port `8081` (default port) and metrics will be available at `http://localhost:8081/metrics`.\n\n## Using with a logger\n\n### Pino with instrumentation\n\nThis approach uses otel instrumentation to automatically inject spanId and traceId.\n\n```ts\nimport { PinoInstrumentation } from '@opentelemetry/instrumentation-pino';\n\nconst otelSDK = new NodeSDK({\n  instrumentations: [new PinoInstrumentation()],\n});\n```\n\n### Pino with custom formatter\n\nThis approach uses the global trace context for injecting SpanId and traceId as a property of your structured log.\n\n```ts\nimport Pino, { Logger } from 'pino';\nimport { LoggerOptions } from 'pino';\nimport { trace, context } from '@opentelemetry/api';\n\nexport const loggerOptions: LoggerOptions = {\n  formatters: {\n    log(object) {\n      const span = trace.getSpan(context.active());\n      if (!span) return { ...object };\n      const { spanId, traceId } = trace.getSpan(context.active())?.spanContext();\n      return { ...object, spanId, traceId };\n    },\n  },\n};\n\nexport const logger: Logger = Pino(loggerOptions);\n```\n\n## Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/pragmaticivan/nestjs-otel.svg)](https://starchart.cc/pragmaticivan/nestjs-otel)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragmaticivan%2Fnestjs-otel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpragmaticivan%2Fnestjs-otel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragmaticivan%2Fnestjs-otel/lists"}