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/
Table of contents
- OpenTelemetry Angular Interceptor
Getting started
Content
This library offers two possibilities to use it in Angular App :
- Interceptor : catch every external call with the HttpClient from angular
- Instrumentation : use instrumentation from opentelemetry-js with web plugins (You need to install and configure it) like :
- @opentelemetry/instrumentation-document-load
- @opentelemetry/instrumentation-fetch
- @opentelemetry/instrumentation-xml-http-request
- ...
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
[SemanticResourceAttributes.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
- url: (string) url of opentelemetry collector (default : http://localhost:4318/v1/traces)
- headers: list of custom header (more info: https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-http)
- concurrencyLimit (string) : An optional limit on pending requests (more info : https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-http)
- timeoutMillis (string): Maximum time the OTLP exporter will wait for each batch export. The default value is 10000ms (more info : https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-http)
Jaeger Propagator Configuration
- customHeader: (string) custom header (more info : https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-jaeger)
Zipkin Exporter Configuration
- url: (string) url of zipkin collector (default : http://localhost:9411/api/v2/spans)
- headers: list of custom header (more info : https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-zipkin)
B3 Propagator Configuration
- multiHeader : (string) Single or Multi Header for b3propagator (default: multi). Value : 'O' (single), '1' (multi) (more info: https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-b3)
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.
- Exporter Module : to define type and export of traces.
- Propagator Module : to define propagation in your HTTP header.
- Last Module, 2 choices :
- OpenTelemetryInterceptorModule : to activate interceptor in all your http call.
- OtelWebTracerModule : to activate instrumentation (you need a component to activate it, see Component otel-instrumentation).
Commons Module
You add this modules in your application module (generally app.module.ts)
Exporter module
There is 4 exporters:
- NoopSpanExporterModule : This a fake exporter
- OtelColExporterModule : OpenTelemetry exporter (more info : https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-exporter-trace-otlp-http)
- ConsoleSpanExporterModule : Console Exporter
- ZipkinExporterModule : Zipkin Exporter (more info : https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-exporter-zipkin)
Propagator module
there is 6 propagators (more info about propagator: https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-core)
- NoopHttpTextPropagatorModule : This is a fake propagator
- B3PropagatorModule : Use B3 propagator
- W3CTraceContextPropagatorModule : Use W3CTraceContext propagator
- JaegerHttpTracePropagatorModule : Use JaegerHttpPropagator (more info about this one: https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-propagator-jaeger)
- AwsXrayPropagatorModule : Use AWS X-Ray propagator
- CompositePropagatorModule : use all of the propagator
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.
- OTEL_EXPORTER : token to inject an implementation of
IExporter
- OTEL_PROPAGATOR : token to inject an implementation of
IPropagator
- OTEL_CONFIG : token to inject an
OpenTelemetryConfig
- OTEL_INSTRUMENTATION_PLUGINS : token to inject an
InstrumentationOption
array - OTEL_LOGGER : more info in (Optional) Logging in OtelColExporterModule
- OTEL_CUSTOM_SPAN : more infor in (Optional) Add span attributes during interception
(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 |