Skip to content

jufab/opentelemetry-angular-interceptor

Repository files navigation

OpenTelemetry Angular Interceptor

@jufab/opentelemetry-angular-interceptor is an Angular Library to deploy OpenTelemetry in your Angular application

This library uses opentelemetry-js package

Use Angular >= 13.0.0

More info : https://jufab.github.io/opentelemetry-angular-interceptor/

npm version codecov

Table of contents

Getting started

Content

This library offers two possibilities to use it in Angular App :

Installation

With npm :

npm i @jufab/opentelemetry-angular-interceptor

Configuration

Use the "OpentelemetryConfig" interface to configure the Tracer

export interface OpenTelemetryConfig {
  commonConfig: CommonCollectorConfig;
  batchSpanProcessorConfig?: BatchSpanProcessorConfig;
  otelcolConfig?: OtelCollectorConfig;
  jaegerPropagatorConfig?: JaegerPropagatorConfig;
  zipkinConfig?: ZipkinCollectorConfig;
  b3PropagatorConfig?: B3PropagatorConfig;
  ignoreUrls?: IgnoreUrlsConfig;
}

Example global Configuration

From the interceptor-example

opentelemetryConfig: {
    commonConfig: {
      console: true, //(boolean) Display trace on console
      production: false, //(boolean) Send trace with BatchSpanProcessor (true) or SimpleSpanProcessor (false)
      logBody: true, //(boolean) true add body in a log, nothing otherwise
      serviceName: 'interceptor-example', //Service name send in trace
      resourceAttributes: { // extra resource attributes like service.namespace
        [ATTR_SERVICE_NAMESPACE]: 'namespace'
      },
      probabilitySampler: '0.7', //Samples a configurable percentage of traces, string value between '0' to '1'
      logLevel:DiagLogLevel.ALL //(Enum) DiagLogLevel is an Enum from @opentelemetry/api
    },
    batchSpanProcessorConfig: { //Only if production = true in commonConfig
      maxQueueSize: '2048', // The maximum queue size. After the size is reached spans are dropped.
      maxExportBatchSize: '512', // The maximum batch size of every export. It must be smaller or equal to maxQueueSize.
      scheduledDelayMillis: '5000', // The interval between two consecutive exports
      exportTimeoutMillis: '30000', // How long the export can run before it is cancelled
    },
    otelcolConfig: {
      url: 'http://localhost:4318/v1/traces', //URL of opentelemetry collector
    },
    jaegerPropagatorConfig: {
      customHeader: 'custom-header',
    }
  }

From the instrumentation-example

backendApp.get('/api/config', (req,res) => {
  return res.status(200).send({
    commonConfig: {
      console: true, // Display trace on console
      production: true, // Send Trace with BatchSpanProcessor (true) or SimpleSpanProcessor (false)
      serviceName: 'instrumentation-example', // Service name send in trace
      resourceAttributes: { // extra resource attributes like service.namespace
        'service.namespace': 'namespace'
      },
      probabilitySampler: '0.75', // 75% sampling
      logLevel: 99 //ALL Log, DiagLogLevel is an Enum from @opentelemetry/api
    },
    otelcolConfig: {
      url: 'http://localhost:4318/v1/traces', // URL of opentelemetry collector
    }
  });
})

Common Configuration

  • console: (boolean) Display trace on console if true
  • production: (boolean)Send trace via BatchSpanProcessor (Async) or SimpleSpanProcessor (Sync) : It's recommend to use BatchSpanProcessor on Production.
  • serviceName: (string) Service name in your trace
  • resourceAttributes: list of extra resource attributes
  • probabilitySampler: (string) Samples a configurable percentage of traces, value between 0 to 1
  • logBody: (boolean) true add body in a log, nothing otherwise
  • logLevel: (DiagLogLevel) log level

BatchSpanProcessor Configuration

This configuration applies if production is true in commonConfig.

  • maxQueueSize: (string) The maximum queue size. After the size is reached spans are dropped.
  • maxExportBatchSize: (string) The maximum batch size of every export. It must be smaller or equal to maxQueueSize.
  • scheduledDelayMillis: (string) The interval between two consecutive exports
  • exportTimeoutMillis: (string) How long the export can run before it is cancelled

OpenTelemetry-collector Configuration

Jaeger Propagator Configuration

Zipkin Exporter Configuration

B3 Propagator Configuration

Ignore URL Configuration

  • urls : (Array<string | RegExp>) URLs that partially match any regex in ignoreUrls will not be traced. In addition, URLs that are exact matches of strings in ignoreUrls will also not be traced

External Configuration

Instrumentation example project have an external configuration to show how you can do it.

Angular module

You need 3 modules to add to your application.

Commons Module

You add this modules in your application module (generally app.module.ts)

Exporter module

There is 4 exporters:

Propagator module

there is 6 propagators (more info about propagator: https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-core)

Interceptor Module

Just add OpenTelemetryInterceptorModule to insert Interceptor

import { NgModule } from '@angular/core';
...
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { OpenTelemetryInterceptorModule, OtelColExporterModule, CompositePropagatorModule } from '@jufab/opentelemetry-angular-interceptor';
import { environment } from '../environments/environment';
...

@NgModule({
  declarations: [AppComponent, ...],
  imports: [
    ...
    HttpClientModule,
    //Insert module OpenTelemetryInterceptorModule with configuration, HttpClientModule is used for interceptor
    OpenTelemetryInterceptorModule.forRoot(environment.opentelemetryConfig),
    //Insert OtelCol exporter module
    OtelColExporterModule,
    //Insert propagator module
    CompositePropagatorModule,
    ...
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Instrumentation Module

Declare this OtelWebTracerModule to configure instrumentation.

You need to provide Web instrumentation on the OTEL_INSTRUMENTATION_PLUGINS token in providers section of NgModule

Example in instrumentation-example project

...
import { OtelColExporterModule, CompositePropagatorModule, OtelWebTracerModule } from 'projects/opentelemetry-interceptor/src/public-api';
...

@NgModule({
  declarations: [AppComponent, ...],
  imports: [
    ...
    // OtelCol Exporter Module
    OtelColExporterModule,
    // Composite Propagator Module
    CompositePropagatorModule,
    // OtelWebTracerModule to configure instrumentation component.
    OtelWebTracerModule.forRoot(environment.openTelemetryConfig),
    ...
  ],
  providers: [
    {provide: OTEL_INSTRUMENTATION_PLUGINS, useValue: [new XMLHttpRequestInstrumentation()]}
  ],
  bootstrap: [AppComponent],
})
export class AppModule { }

This module uses APP_INITIALIZER token to load instrumentation (multi:true). No component needs now

Interceptor Module And Instrumentation Module

Don't use them at the same time : you're going to have the same trace twice.

Injection token

This library exposes injection token. You can use them to override or customize.

(Optional) Logging in OtelColExporterModule

You can add a logger to the OtelColExporterModule with the OTEL_LOGGER token.

You can use a custom logger which implements the DiagLogger in @opentelemetry/api.

Or, you can use an existing logger which implements the same functions (error, warn, info, debug) like ngx-logger.

NGXLogger

You can use ngx-logger.

In your appModule, insert LoggerModule and configure it

@NgModule({
  ...
  imports: [
    LoggerModule.forRoot(environment.loggerConfig),
  ]
  ...

And use OTEL_LOGGER token to inject NGXLogger

@NgModule({
  ...
  providers: [
    ...
    { provide: OTEL_LOGGER, useExisting: NGXLogger }
    ...
  ]

Don't forget to set "logLevel" in Common Configuration (Level must be the same between NGXLogger and common configuration)

You can see an example in the interceptor-example.

(Optional) Add span attributes during interception

This option is only available for Interceptor Module

Implement a CustomSpan and the method add(span: Span, request: HttpRequest<unknown>, response: HttpResponse<unknown> | HttpErrorResponse): Span

  • span : Current span, you can set or get attributes
  • request : Current request in interceptor
  • response : Current response in interceptor

Implement CustomSpan class like :

class CustomSpanImpl implements CustomSpan {
  add(span: Span, request: HttpRequest<unknown>, response: HttpResponse<unknown> | HttpErrorResponse): Span {
    span.setAttribute('mycustom.key', request.params + ";" + response.status);
    return span;
  }
}

Inject it in you App module with OTEL_CUSTOM_SPAN :

@NgModule({
  ...
  providers: [
    ...
    { provide: OTEL_CUSTOM_SPAN, useClass: CustomSpanImpl }
    ...
  ]

You can see an example in the interceptor-example.

How it works

This library is based on HttpClientModule and the HTTP_INTERCEPTORS

OpenTelemetryInterceptor implement an HttpInterceptor and the intercept method.

This implementation initialise a WebTracerProvider, create a Span and add header propagation in the current call.

The response body is adding by an event in span.

Example

This project has two example Angular Application:

You can see how configure and insert all modules.

You can althought test opentelemetry-angular-interceptor with this two applications.

Run

Interceptor

To start this Interceptor example application, run command :

npm run start:complete-interceptor-example

and open the application at http://localhost:4200

Instrumentation

To start this Instrumentation example application, run command :

npm run start:complete-instrumentation-example

and open the application at http://localhost:4200

[Optional] Result in OpenTelemetry-collector

If you want to see the result in a collector *, there's a docker-compose available in this project.

You can start it with this command :

docker-compose -f collector/docker-compose.yaml up -d

Go to the jaeger application (http://localhost:16686) to see result.

More info about the collector here : https://github.com/open-telemetry/opentelemetry-collector

* without an Agent or a Collector you can see an error in your browser about sending a "trace".

Troubleshoot

Angular 10 Warning

WARNING in xxx/fesm2015/jufab-opentelemetry-angular-interceptor.js depends on '@opentelemetry/web'. CommonJS or AMD dependencies can cause optimization bailouts.
For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies

WARNING in xxx/fesm2015/jufab-opentelemetry-angular-interceptor.js depends on '@opentelemetry/core'. CommonJS or AMD dependencies can cause optimization bailouts.
For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies

WARNING in xxx/fesm2015/jufab-opentelemetry-angular-interceptor.js depends on '@opentelemetry/tracing'. CommonJS or AMD dependencies can cause optimization bailouts.
For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies

WARNING in xxx/fesm2015/jufab-opentelemetry-angular-interceptor.js depends on '@opentelemetry/api'. CommonJS or AMD dependencies can cause optimization bailouts.
For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies

WARNING in xxx/fesm2015/jufab-opentelemetry-angular-interceptor.js depends on '@opentelemetry/exporter-collector/build/src/platform/browser'. CommonJS or AMD dependencies can cause optimization bailouts.
For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies

Add to your angular.json

"options": {
  "allowedCommonJsDependencies": [
    "@opentelemetry/api",
    "@opentelemetry/exporter-collector",
    "@opentelemetry/exporter-zipkin",
    "@opentelemetry/tracing",
    "@opentelemetry/web",
    "@opentelemetry/core",
    "@opentelemetry/propagator-jaeger",
    "@opentelemetry/propagator-b3",
    "@opentelemetry/instrumentation",
    "@opentelemetry/instrumentation-xml-http-request",
    "@opentelemetry/instrumentation-document-load",
    "@opentelemetry/instrumentation-fetch",
    "@opentelemetry/context-zone-peer-dep"
  ],

Other

Error Fix
error TS2694: Namespace 'NodeJS' has no exported member 'Timeout'. Need dependence @type/node >= 12.0.2
error TS1086: An accessor cannot be declared in an ambient context. Need dependence typescript >= 3.6.0