Adding OpenTelemetry to an Angular Application

profile
Tim Deschryver
timdeschryver.dev

OpenTelemetry is a part of Cloud Native Computing Foundation (CNCF), and is the standard to monitor your application.

format_quote

OpenTelemetry makes robust, portable telemetry a built-in feature of cloud-native software.

With the use of Signals, which are traces, logs, and metrics, OpenTelemetry gives you a good insight into the state of the application. This helps you to get a better understanding of the application and can help you to detect problems and possible problems in the future. There are official packages for most of the ecosystems, including one for JavaScript (with TypeScript support) in a Node.js environment and one to use in a browser context, the latter is the one that we'll explore in this blog post.

OpenTelemetry is something I discovered at the end of 2022 and didn't know I needed. It's high up on my list of technologies that I want to explore and know better this year. While there is already useful content surrounding this topic, and the official documentation also provides lots of goodies in the sorts of API documentation, guides, and examples, I might write about my experience with OpenTelemetry in the future. But for frontend applications using TypeScript, I find that there's a lack of information about using OpenTelemetry. That's why I want to already take the time and share how I added OpenTelemetry to an application built with Angular.

In this blog post we learn how to configure the OpenTelemetry SDK to get graphs like the following.

A trace graph containing HTTP requests triggered by an initial click event.

Setup link

I followed the "Getting Started" documentation to add OpenTelemetry to my application. This leads to the following bare code, in which some parts are left out but will be covered on later.

The code registers two main essentials:

To follow along with me, install the initial packages that are used in the above snippet:

Creating Traces with Instrumentations link

A trace represents a collection of events (spans is the correct term) that are related to each other. It usually starts with an initial event that is invoked by an end user.

format_quote

Traces give us the big picture of what happens when a request is made by a user or an application.

We can for example create a trace when a button is clicked. But, instead of writing this code manually, I'm using the @opentelemetry/auto-instrumentations-web package.

@opentelemetry/auto-instrumentations-web bundles some packages and does most of the heavy lifting for us. It automatically creates traces for most of the important events, this includes:

format_quote

The @opentelemetry/auto-instrumentations-web package can also serve as a solid base to introduce the usefulness of OpenTelemetry to your team.

To start collecting the events add the exported getWebAutoInstrumentations method to the instrumentations collection. By default, this creates traces for all of the above events, but you can choose to opt out of individual packages. The following example uses the default configuration for all packages.

To follow along with me, install the additional packages that are used in the above snippet:

format_quote

Depending on the version you need to enable the skipLibCheck tsconfig option because the package may contain invalid types.

Sending Traces with Processors and Exporters link

Now that the instrumentations are plugged in and are creating traces, we need to start sending them somewhere so they can be useful. You can either send them directly to a backend, or to your hosted collector (recommended).

To send the traces (and later logs and metrics), we use one or more processors (to transform/batch signals) and an exporter (how to send signals).

To give a good overview to see which traces are created and when they're exported, the most simple solution is to log them to the console. But this isn't useful in a production environment, and it also isn't visual and doesn't show you the big picture.

Instead, I encourage you to directly use a solution that is used in a production environment, and which is also capable to analyze the collected telemetry data. This way you also get the hang of how to use your tools that are used in production for when it really matters. Luckily, there is no difference between the signals that are logged to the console and the ones that are sent to a backend/collector.

The good tools also immediately show you the signals that are received without any delay. This means you can see the signals in real time, just like in the console. The big benefit of this is that the signals are aggregated into views and graphs that give you a better overview. They show you when the signal is created, together with its parent and children.

You can spin up your own collector and/or backend, or use a cloud-based solution. When I dove into OpenTelemetry I used Honeycomb, which is used in the next examples.

format_quote

You can create a free account in the Honeycomb application

To send the traces add a processor and exporter to the web tracer provider. In the following example, the ConsoleSpanExporter export is used to log the traces, and the OTLPTraceExporter exporter is used to send them to Honeycomb.

The ConsoleSpanExporter exporter uses a SimpleSpanProcessor, while the OTLPTraceExporter exporter uses the BatchSpanProcessor processor. The latter batches the traces before handing them to the exporter, while the SimpleSpanProcessor processor directly forwards the traces.

To follow along with me, install the additional packages that are used in the above snippet:

Traces overview link

To start instrumenting your Angular application, create a new instrument.ts file and copy-paste the current state of the snippet into it.

Next, import the file into the main.ts file.

Now, when you load the application you should see some traces in the console. These traces hold the information about the document load. The traces should also be visible in honeycomb (we'll see how this looks like later).

Let's also add a button to fetch some resources. For this, add the following code to the AppComponent.

Now, when you click the button you should see more traces coming in. The new traces include user interactions (because you clicked the button), and requests (to the JSON placeholder API).

You also see these traces in honeycomb.

The honecomb dashboard, which shows the traces from our Angular application

When we click a trace to see the details, we can see that the trace contains a lot of information. We can for example, when we open the click event we can see which element is clicked and that the click triggered a fetch request.

A trace graph containing more details about the event, and the underlying events that are triggered by this event

We can also see what API endpoint was called, including the response status and response time.

More details about the fetch trace

Zone Context link

But, there's room for improvement. In the example used in this post, we can see that the click triggers an HTTP request, that actually results in multiple requests. Only the initial request shows up in the graph, and the rest of the requests are not visible. They are however visible as individual traces.

This is because a trace is not aware of the context in which it was created. To fix this, we need to introduce zones.

If you're familiar with zones in Angular this should ring a bell. Within a zone, all underlying traces are aware of the parent. This means that the click event creates a zone, and all the HTTP requests within the zone are grouped in this zone.

format_quote

Fun fact: it uses Angular's zone.js!

This results in a trace that includes all the HTTP requests triggered by the click, which looks like this:

A trace graph containing all underlying events triggered by the initial click event.

Conclusion link

That's it!

With the help of the packages provided by OpenTelemetry we can now see what's going on in our Angular application. We didn't have to write custom code to instrument our application, we just had to add a few lines to configure the OpenTelemetry SDK.

In the next posts we'll look at how to make this code more "Angulary", include more information in our traces to make it easier to monitor, how to log errors, and more.

Incoming links

Feel free to update this blog post on GitHub, thanks in advance!

Join My Newsletter (WIP)

Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.

Support me

I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.

Buy Me a Coffee at ko-fi.com PayPal logo

Share this post

Twitter LinkedIn