Ionic Global Error Handler

March 30th 2018 Ionic 2/3 Angular

During development (when using ionic serve), Ionic replaces the default Angular error handler with its own implementation which displays the error details in a page overlay instead of just printing them out to console. However, as soon as you build the app for the mobile device, you're back to the default error handler.

By intercepting all outputs to console you could provide an easy access to these errors even on the device. But since we are talking about unhandled errors, you might want to give them even more attention, e.g. automatically report them to your analytics service. If you implement your own error handler and use it to replace the default one, you can achieve just that.

The basics are simple. You implement or extend Angular's ErrorHandler class. I chose the latter to keep the default logging to console intact. There's only a single method to implement: handleError.

import { ErrorHandler, Injectable } from '@angular/core';

@Injectable()
export class GlobalErrorHandler extends ErrorHandler {

  constructor() {
    super();
  }

  handleError(error: any): void {
    super.handleError(error);
  }
}

We haven't added any custom functionality yet, but let's first register our handler instead of the one from Ionic. Find the following entry in the AppModule's providers list:

{provide: ErrorHandler, useClass: IonicErrorHandler}

And replace it with your own:

{provide: ErrorHandler, useClass: GlobalErrorHandler}

You'll lose the custom error overlay during development. I don't mind that. But if you do, you can extend IonicErrorHandler instead.

Let's get back to implementing our error handler now. I chose Google Analytics for reporting the errors:

import { GoogleAnalytics } from '@ionic-native/google-analytics';
import { ErrorHandler, Injectable } from '@angular/core';

@Injectable()
export class GlobalErrorHandler extends ErrorHandler {

  constructor(private ga: GoogleAnalytics) {
    super();
  }

  handleError(error: any): void {
    super.handleError(error);
    this.ga.trackException(JSON.serialize(error), false);
  }
}

I described how to set up and initialize Google Analytics in Ionic in a previous blog post.

This simple implementation has some flaws, though:

  1. Most of Error object properties aren't serialized by default. To log more information, you should pass your own replacer function to JSON.serialize().

  2. When the error is thrown from a promise, attempting to serialize the resulting Error object will likely result in an error because of a circular JSON structure. Since the thrown Error object is also just a wrapper for the original error details I'm really interested in, I decided to only report that and avoid the serialization issue:

     if (error.rejection) {
       error = error.rejection;
     }
    
  3. Google Analytics restricts the maximum length of a tracked exception. If that length is exceeded, nothing gets logged. To deal with that issue, I decided to parse the relevant information and only report that.

     export interface ParsedError {
       message: string;
       status?: number;
       stack?: string;
     }
    
     parse(error: any): ParsedError {
    
       // get best available error message
       let parsedError: ParsedError = {
           message: error.message ? error.message as string : error.toString()
       };
    
       // include HTTP status code
       if (error.status != null) {
           parsedError.status = error.status;
       }
    
       // include stack trace
       if (error.stack != null) {
           parsedError.stack = error.stack;
       }
    
       return parsedError;
     }
    

With all that in place, here's the resulting error handler implementation:

handleError(error: any): void {
  super.handleError(error);

  // unroll errors from promises
  if (error.rejection) {
    error = error.rejection;
  }

  let parsedError = this.parse(error);
  this.ga.trackException(JSON.serialize(parsedError), false);
}

And I don't need to use a custom replacer function for JSON serialization any more, because I'm now serializing my custom ParsedError object instead of a JavaScript Error object.

Copyright
Creative Commons License