Proactive monitoring with Angular and Datadog

Being proactive is essential for any application whether that concerns the API, the web or mobile application. You can capture errors as they happen, with zero involvement from application users. Then of course you can work to fix the error, contact end users or whatever might seem appropriate on each case.
Cover image for Proactive monitoring with Angular and Datadog
Photo by Ibrahim Boran on Unsplash

As part of this article we examine how we can setup proactive monitoring using Angular 12 and Datadog, by following two different approaches. The implementation should be similar for React and Vue and of course this can be adjusted to use Sentry or any other Datadog alternative.

Datadog is mostly popular as a cloud and infrastructure monitoring but it can be also used as a centralized logging platform. Personally I find that you get better application insights when you combine infrastructure and application logs into one platform.

Before getting started, you will need to install @datadog/browser-logs.

npm i @datadog/browser-logs

Using forwardErrorsToLogs

The easier way to get started is to initialize Datadog with forwardErrorsToLogs enabled for production, or any non-dev environment. With this approach you will be forwarding error logs to datadog, on top of outputting the errors in console.

As you can tell this approach is not Angular specific. Nevertheless here is how this can be done in any Angular project.

1import { datadogLogs as datadog } from '@datadog/browser-logs';
2 
3if (environment.production) {
4 enableProdMode();
5 
6 datadog.init({
7 clientToken: environment.datadog.clientToken,
8 site: environment.datadog.site,
9 service: environment.datadog.service,
10 forwardErrorsToLogs: true,
11 sampleRate: 100,
12 });
13}

However, there is a main downside with this approach — there is no control over to what happens behind the scenes. For example, it is not possible to forward a warning message, or include extra details in context or even scrub sensitive data.

Also to make that work, the datadog library overwrites console.error which might have side effects if any other package does the same.

Using Logger service

Another approach is to wrap Datadog into a Logger service which we can inject later on via Dependency Injection. There are a few benefits with the approach. Firstly, we can now forward any type of messages, not only errors. Secondly, the Datadog dependency is well hidden from the rest of the application. Last, but not least, we can provide a default implementation when Datadog configuration is not available.

1import { Injectable } from '@angular/core';
2import { datadogLogs as datadog } from '@datadog/browser-logs';
3import { environment } from './../environments/environment';
4 
5@Injectable({
6 providedIn: 'root',
7})
8export class Logger {
9 private initialized = false;
10 constructor() {
11 if (!environment?.datadog) {
12 return;
13 }
14 
15 datadog.init({
16 clientToken: environment.datadog.clientToken,
17 site: environment.datadog.site,
18 service: environment.datadog.service,
19 forwardErrorsToLogs: true,
20 sampleRate: 100,
21 });
22 this.initialized = true;
23 }
24 
25 public debug(message: string, context?: { [x: string]: any }): void {
26 if (this.initialized) {
27 datadog.logger.debug(message, context);
28 }
29 }
30 
31 public info(message: string, context?: { [x: string]: any }): void {
32 if (this.initialized) {
33 datadog.logger.info(message, context);
34 }
35 }
36 
37 public warn(message: string, context?: { [x: string]: any }): void {
38 if (this.initialized) {
39 datadog.logger.warn(message, context);
40 }
41 }
42 
43 public error(message: string, context?: { [x: string]: any }): void {
44 if (this.initialized) {
45 datadog.logger.error(message, context);
46 }
47 }
48}

The same service can be used in a custom error handler that intercepts error handling to capture errors and forward them to Datadog. Note that the default implementation of ErrorHandler prints error messages to the console.

1import { ErrorHandler, Injectable, isDevMode } from '@angular/core';
2import { Logger } from './logger.service';
3 
4@Injectable({
5 providedIn: 'root',
6})
7export class MyErrorHandler implements ErrorHandler {
8 constructor(private logger: Logger) {}
9 
10 public handleError(error: any): void {
11 if (isDevMode()) {
12 console.log(error);
13 }
14 
15 this.logger.error(error);
16 }
17}

With the above in place, you should be able to forward custom error messages to Datadog. As mentioned before, the Logger service can be extended to include environment and other context details that are helpful when troubleshooting.

Conclusion

As part of this article we covered two ways of integrating your Angular project with Datadog. Doing so it will allow you to view errors as they happen in Datadog. Combine that with infrastructure/API logs and automated notifications or metrics and you should a basic but yet robust system for proactive monitoring.

Make sure to follow me on dev.toMedium or Twitter to read more about Angular and other dev topics.

Leave a Reply

Your email address will not be published. Required fields are marked *

Next Post
Cover image for Faster Docker builds with composer install

Faster Docker builds with composer install ⚡

Related Posts