@swimlane/opentelemetry-angular-interceptor

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

Usage no npm install needed!

<script type="module">
  import swimlaneOpentelemetryAngularInterceptor from 'https://cdn.skypack.dev/@swimlane/opentelemetry-angular-interceptor';
</script>

README

OpenTelemetry Angular Interceptor

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

This library uses opentelemetry-js package

Only works for Angular >= 9.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 @opentelemetry/api @opentelemetry/web @opentelemetry/tracing @opentelemetry/core @opentelemetry/semantic-conventions @opentelemetry/resources @opentelemetry/exporter-collector @opentelemetry/exporter-zipkin @opentelemetry/propagator-b3 @opentelemetry/propagator-jaeger @opentelemetry/context-zone-peer-dep @opentelemetry/instrumentation @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request

Configuration

Use the "OpentelemetryConfig" interface to configure the Tracer

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

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
      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:55681/v1/traces', //URL of opentelemetry collector
    },
    jaegerPropagatorConfig: {
      customHeader: 'custom-header',
    }
  }

From the instrumentation-example

export const environment: IEnvironment = {
  production: false,
  urlTest: 'http://localhost:4200/api',
  openTelemetryConfig: {
    commonConfig: {
      console: true, // Display trace on console
      production: false, // Send Trace with BatchSpanProcessor (true) or SimpleSpanProcessor (false)
      serviceName: 'instrumentation-example', // Service name send in trace
      probabilitySampler: '0.75', // 75% sampling
      logLevel: DiagLogLevel.ALL //ALL Log, DiagLogLevel is an Enum from @opentelemetry/api
    },
    otelcolConfig: {
      url: 'http://localhost:55681/v1/traces', // URL of opentelemetry collector
    },
    instrumentationConfig: {
      xmlHttpRequest: true,
      fetch: true,
      documentLoad: true,
    }
  }
};

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
  • 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

  • url: (string) url of zipkin collector (default : http://localhost:9411/api/v2/spans)
  • headers: list of custom header (more info : hhttps://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-zipkin)

B3 Propagator Configuration

Instrumentation Configuration

this configuration is only for the instrumentation Mode

  • xmlHttpRequest: (boolean) Activate XmlHttpRequest plugin
  • fetch:(boolean) Activate fetch plugin
  • documentLoad: (boolean) Activate documentLoad plugin

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 3 exporters:

Propagator module

there is 5 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

...
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: [],
  bootstrap: [AppComponent],
})
export class AppModule { }

Interceptor Module And Instrumentation Module

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

Component otel-instrumentation

A component named otel-instrumentation must be add in your application.

In instrumentation-example, this component is in app.component.html like this :

...
<otel-instrumentation></otel-instrumentation>
...

there is no configuration/directive need : all is in OtelWebTracerModule

(Optional) Logging in OtelColExporterModule

You can add a logger to the OtelColExporterModule with the OTELCOL_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 OTELCOL_LOGGER token to inject NGXLogger

@NgModule({
  ...
  providers: [
    ...
    { provide: OTELCOL_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 CUSTOM_SPAN :

@NgModule({
  ...
  providers: [
    ...
    { provide: 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

Otel Collector

Otel Collector 0.29.0 is not compatible with this library in version 0.23.0.

A bug fix is in progress here: https://github.com/open-telemetry/opentelemetry-js/issues/2321

Otel Collector 0.28.0 is OK.

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