Monitor your Angular app with Application Insights


You've built an amazing product or app. Now it's live time but how do you support an application in production? On part of the answer to that question is with a good monitoring solution. There are plenty of commercial product tackling that matter. In this article we will talk about how to leverage Application Insights to monitor a modern angular app, the product offered as part of Azure.

What are we interested in collecting ? Well to be in a good position to support an application you need to be able to know what your users are doing and if they are encountering any issues. In this article we will see of to report routing events and unhandled errors data.

Install the dependencies

Install Application Insights Client:

yarn add applicationinsights-js @types/applicationinsights-js 

Write the MonitoringService

First you need to create the service with the CLI:

ng g service monitoring

It will create a monitoring.service.ts file, in which we can start writing our logic.

Initializing AppInsights

  constructor(
      private router: Router,
      private activatedRoute: ActivatedRoute
  ) {
       if (environment.appInsightsConfig && environment.appInsightsConfig.instrumentationKey) {
          AppInsights.downloadAndSetup(environment.appInsightsConfig);
      }
  }

Logging convenience methods

First we will add 3 convenience methods to make it easy to log Events, Page Views and errors.

The AddGlobalProperties will be the place additional data we want to add to systematically to the logs. It is a good place to add the application version information.

  public logPageView(
      name: string,
      url?: string,
      properties?: { [key: string]: string },
      measurements?: { [key: string]: number },
      duration?: number) {

      AppInsights.trackPageView(name, url, this.AddGlobalProperties(properties), measurements, duration);
  }

  public logEvent(name: string, properties?: { [key: string]: string }, measurements?: { [key: string]: number }) {
      AppInsights.trackEvent(name, this.AddGlobalProperties(properties), measurements);
  }

  public logError(error: Error, properties?: { [key: string]: string }, measurements?: { [key: string]: number }) {
      AppInsights.trackException(error, null, this.AddGlobalProperties(properties), measurements);
  }

  private AddGlobalProperties(properties?: { [key: string]: string }): { [key: string]: string } {
      if (!properties) {
          properties = {};
      }

      //add your custom properties such as app version

      return properties;
  }

Track authenticated users

In many cases we want to have the information if a user is authenticated or not. In order to achieve that, we will add a method which we'll call after a successful login:

  setAuthenticatedUserId(userId: string): void {
      AppInsights.setAuthenticatedUserContext(userId);
  }

Automatically track router events

In order to do so, we will subscribe to the router events and log pageviews when and event occurs.

  this.routerSubscription = this.router.events
        .filter(event => event instanceof ResolveEnd)
        .subscribe((event: ResolveEnd) => {
            const activatedComponent = this.getActivatedComponent(event.state.root);
            if (activatedComponent) {
                this.logPageView(`${activatedComponent.name} ${this.getRouteTemplate(event.state.root)}`, event.urlAfterRedirects);
            }
        });

In this example, we trace the component of the inner most route and the route template. To do so we use the two following methods:

 private getActivatedComponent(snapshot: ActivatedRouteSnapshot): any {

      if (snapshot.firstChild) {
          return this.getActivatedComponent(snapshot.firstChild);
      }

      return snapshot.component;
  }

  private getRouteTemplate(snapshot: ActivatedRouteSnapshot): string {
      let path = '';
      if (snapshot.routeConfig) {
          path += snapshot.routeConfig.path;
      }

      if (snapshot.firstChild) {
          return path + this.getRouteTemplate(snapshot.firstChild);
      }

      return path;
  }

Handle and report errors

Angular provide a mechanism to intercept errors. To do so you must override the default ErrorHandler provider.

Our custom ErrorHandler

In our custom handler which will inherit form Angular default implementation, we will lazily get the MonitoringService, send the error log and call the default angular logic:

@Injectable()
export class MonitoringErrorHandler extends ErrorHandler {
    constructor(private injector: Injector) {
        super();
    }

    handleError(error: any): void {
        const monitoringService = this.injector.get(MonitoringService);
        monitoringService.logError(error);
        super.handleError(error);
    }
}

Configuration the ErrorHandler

Now we have a handler, we need to tell angular how to use it. To achieve that you must add it to the providers of your root AppModule.

 providers: [
    //...
    {
      provide: ErrorHandler,
      useClass: MonitoringErrorHandler
    }
    //...
  ],

What about the results !

Well I won't go through the many great feature application Insights offers which may be the subject of future posts. Instead I'll just show you one of my favorite feature which is the failure (still on preview but already so good).

It starts with a dashboard of the recent failure. You can see the status codes, the error types and it can also detect dependency failures. It really allows fast troubleshooting for most issues you may encounter.

Here I notice We have on 500 which usually indicates something really bad. (and likely to be unhandled).

Let's check the request logs. It will display all the information which have been collected about the request. Including Exception stack trace.

But there's more. Application Insights is able to correlate our angular and api calls so the User Id and Session Id links are browsable. My favorite is Session which will show what led the user to that unfortunate destiny and what he did next!

You can find a sample project on github

Let us know what you think !