Softwareentwicklung Zürich & Deutschschweiz

Setup sentry application monitoring in an angular universal project

29.04.2021
When going live with an application we very often come up with the question how reliable it really works on the clients machines. The most important thing for the operators of the application is to monitor crashes and erros cuased by the application.
In this year's ng enterprise conference I learned about sentry.io. This is a great tool, to track all the application crashes, link them to commits on GitHub and show the stack trace and the users behavior. Also it provides some specials like a crash report dialog etc.
In this blog post I want to show you how to include sentry into your angular universal application as well as publishing the needed resources when deploying the application using github actions.
Sentry is also awailable for a lot of other platforms like dotnet core etc. Please take a look at the sentry.io homepage here for more information.

Getting started

First of all you need to add the npm packages to your application. Therefore run:
npm install --save @sentry/angular @sentry/tracing

Configuration

To initialize sentry in your angular application you simply need to add the following code in your main.ts file.
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import * as Sentry from "@sentry/angular";
import { Integrations } from "@sentry/tracing";
import { AppModule } from "./app/app.module";

Sentry.init({
  dsn: "https://...." ,
  integrations: [
    // Registers and configures the Tracing integration,
    // which automatically instruments your application to monitor its
    // performance, including custom Angular routing instrumentation
    new Integrations.BrowserTracing({
      tracingOrigins: ["localhost", "https://yourserver.io/api"],
      routingInstrumentation: Sentry.routingInstrumentation,

    }),
  ],

  // Set tracesSampleRate to 1.0 to capture 100%
  // of transactions for performance monitoring.
  // We recommend adjusting this value in production
  tracesSampleRate: 1.0,
});


platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .then(success => console.log('Bootstrap success'))
  .catch(err => console.error(err));
Then provide the sentry ErrorHandler in your module
providers: [
  {
    provide: Sentry.TraceService,
    deps: [Router],
  },
  {
    provide: APP_INITIALIZER,
    useFactory: () => () => {},
    deps: [Sentry.TraceService],
    multi: true,
  },
], 
I also added the trace service here to enable tracing as well.

Changes in angular universal

As you know when using angular universal your angular routes are prerendered on server side. On server side you won't be able to access some common browser elements like window or document. Sentry is accessing some of these 'forbidden' objects. So you need to do some extra effort. Please note that there can be differences when running the app locally (using npm run dev:ssr) or running the app from within a docker container for example.
First create an injectable class which initializes sentry only when running in the browser.
import { isPlatformBrowser } from '@angular/common';
import { ErrorHandler, Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { Integrations } from '@sentry/tracing';
import * as Sentry from '@sentry/angular';
import { environment } from '../environments/environment';

@Injectable()
export class ErrorLogger implements ErrorHandler {
    constructor(@Inject(PLATFORM_ID) private readonly platformId: any) {
      if(isPlatformBrowser(this.platformId)) {
        Sentry.init({
          dsn: '...',
          release: environment.sentry.environment,
          integrations: [
            new Integrations.BrowserTracing({
              tracingOrigins: ['...', '...'],
              routingInstrumentation: Sentry.routingInstrumentation,
            }),
          ],
          tracesSampleRate: 1.0,
        });
      }
  }

  handleError(error: any): void {
    if (isPlatformBrowser(this.platformId)) {
      const eventId = Sentry.captureException(error.originalError || error);
      Sentry.showReportDialog({ eventId });
    }
  }
}
I simply named this file 'error-logger.ts'. To ensure that this is only executed in the browser we need to inject the platform id, so we're able to detect where the application is running at the moment.
Next we are using this class as factory in our app module
providers: [{
    provide: ErrorHandler, useClass: ErrorLogger, deps: []
  },
  {
    provide: Sentry.TraceService,
    deps: [Router],
  },
  {
    provide: APP_INITIALIZER,
    useFactory: () => (): any => { },
    deps: [Sentry.TraceService],
    multi: true,
  }
],
As you can see, we provide the ErrorLogger with the useClass attribute to tell the compiler how this logger should be instantiated. We can leave the TraceService providers as is since they are not crashing in universal.

Publish sentry release using github actions

To create a release and publish it to sentry we need to add some stuff to our Guthub actions pipeline. I am not going to show how to configure sentry itself. This can be found in the docs here
Before we start we need to ensure to also build sourceMaps. This can be done in angular.json. It's important to enable sourceMaps so you will be able to detect the unminified part of your code which caused the application crash.
Now let's have a look at our actions
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{matrix.node-version }}
  uses: actions/setup-node@v1
  with:
    node-version: ${{ matrix.node-version }}
- name: npm install, build
  run: |
    npm install
    npm run build:ssr
- uses: azure/docker-login@v1
  with:
    login-server: ${{env.AZURE_CONTAINER_URL}}
    username: ${{secrets.AZURE_CONTAINER_USERNAME}}
    password: ${{secrets.AZURE_CONTAINER_PASSWORD}}
- name: Build the Docker image
  working-directory: ./
  run: |
    docker build . --file Dockerfile --tag ${{env.AZURE_CONTAINER_URL}}/${{env.BM_TAG}}
    docker push ${{env.AZURE_CONTAINER_URL}}/${{env.BM_TAG}}
- name: Login via Az module
  uses: azure/login@v1.1
  with:
    creds: ${{secrets.AZURE_BM_CREDENTIALS}}
    enable-AzPSSession: false                 
- name: Deploy Azure WebApp
  uses: Azure/webapps-deploy@v2
  with:
    app-name: ${{env.AZURE_BM_WEBAPP_NAME}}
    images: ${{env.AZURE_CONTAINER_URL}}/${{env.BM_TAG}}
- name: Create Sentry release
  uses: getsentry/action-release@v1
  env:
   SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
   SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
   SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
  with:
   environment: prod,
   set_commits: auto
   sourcemaps: dist/
      
First we run npm install and build the application. Next we log in to our azure container registry. This can be different when using dockerhub or any. After that we build the docker image which exposed the ssr port and runs the application. Then we login to azure and push the docker container.
Last but not least the most important thing happens. We can finally create the sentry release and push our sourcemaps (sourcemaps: dist/). Also we do link our github commits with the release (set_commits: auto))
When everything worked you'll see something similar to the following in your sentry project page when an error occurs
If you have any questions or improvements please send me an email to
© 2016-2021 OneCodex GmbH – All rights reserved