Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

Commit 7dd5ed8

Browse files
committed
Custom exception handler service cookbook.
This cookbook demonstrates how to override the core `ExceptionHandler` class with a custom service class that both logs to the console as well as sending errors to a robust error-logginer service. This error-logging service can, in turn, send errors to the server; or, to Software-as-a-Service (SaaS) products like New Relic, TrackJS, and Raygun.
1 parent 596b686 commit 7dd5ed8

File tree

13 files changed

+330
-0
lines changed

13 files changed

+330
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='../_protractor/e2e.d.ts' />
2+
'use strict';
3+
// gulp run-e2e-tests --filter=cb-exception-handler
4+
describe('Handle exceptions', function() {
5+
6+
beforeAll(function () {
7+
browser.get('');
8+
});
9+
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component } from '@angular/core';
4+
5+
// #docregion component
6+
@Component({
7+
selector: 'my-app',
8+
template:
9+
`
10+
<p>
11+
<a (click)="throwError()">Throw an Error</a> <em>(and watch console)</em>.
12+
</p>
13+
`
14+
})
15+
// #docregion class
16+
export class AppComponent {
17+
constructor() {}
18+
19+
public throwError() {
20+
throw(new Error('No Disassemble!'));
21+
}
22+
}
23+
// #enddocregion class
24+
// #enddocregion component
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// #docregion
2+
import { BrowserModule } from '@angular/platform-browser';
3+
import { ExceptionHandler } from '@angular/core';
4+
import { NgModule } from '@angular/core';
5+
6+
import { AppComponent } from './app.component';
7+
import { CustomExceptionHandler } from './custom-exception-handler';
8+
import { ErrorLoggingService } from './error-logging.service';
9+
10+
// #docregion module
11+
@NgModule({
12+
imports: [ BrowserModule ],
13+
providers: [
14+
ErrorLoggingService,
15+
16+
// In order to override the default ExceptionHandler service, we have to provide our
17+
// custom implementation as part of the application module - if we try to do this
18+
// later on, in the App component, it will be too late.
19+
{
20+
provide: ExceptionHandler,
21+
useClass: CustomExceptionHandler
22+
}
23+
],
24+
declarations: [ AppComponent ],
25+
bootstrap: [ AppComponent ]
26+
})
27+
export class AppModule { }
28+
// #enddocregion module
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// #docregion
2+
import { ExceptionHandler } from '@angular/core';
3+
import { Injectable } from '@angular/core';
4+
5+
import { ErrorLoggingService } from './error-logging.service';
6+
7+
// #docregion class
8+
@Injectable()
9+
export class CustomExceptionHandler {
10+
private errorLogggingService: ErrorLoggingService;
11+
12+
constructor(errorLogggingService: ErrorLoggingService) {
13+
this.errorLogggingService = errorLogggingService;
14+
}
15+
16+
// Public methods.
17+
18+
public call(exception: any, stackTrace?: any, reason?: string) {
19+
this.logToConsole(exception, stackTrace, reason);
20+
21+
try {
22+
// Internally, Angular wraps Error objects inside a proxy; therefore, when we
23+
// send the error to the logging service, we want to try and unwrap the error
24+
// first so that we're more likely to send-over a native Error object.
25+
// --
26+
// CAUTION: This is NOT A DOCUMENTED BEHAVIOR - you have to look at the Angular
27+
// source code in order to see that this happens.
28+
this.errorLogggingService.log(this.unwrapError(exception));
29+
} catch (loggingError) {
30+
this.logToConsole(loggingError);
31+
}
32+
}
33+
34+
// Private methods.
35+
36+
private logToConsole(exception: any, stackTrace?: any, reason?: string) {
37+
// Even though we are replacing the core _instance_ of the ExceptionHandler, we can
38+
// still leverage the core class' static method for stringification of the error.
39+
let stringified = ExceptionHandler.exceptionToString(exception, stackTrace, reason);
40+
41+
if (console && console.group && console.log) {
42+
console.group('Custom Exception Handler');
43+
console.log(stringified);
44+
console.groupEnd();
45+
}
46+
}
47+
48+
private unwrapError(exception: any): any {
49+
while (exception.originalException) {
50+
exception = exception.originalException;
51+
}
52+
return(exception);
53+
}
54+
}
55+
// #enddocregion class
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// #docplaster
2+
// #docregion
3+
import { Injectable } from '@angular/core';
4+
5+
// #docregion class
6+
@Injectable()
7+
export class ErrorLoggingService {
8+
constructor() {}
9+
10+
// Public methods.
11+
12+
public log(error: any) {
13+
// Internal tracking.
14+
this.sendToConsole(error);
15+
this.sendToServer(error);
16+
17+
// Software-as-a-Service (SaaS) tracking.
18+
this.sendToNewRelic(error);
19+
this.sendToRaygun(error);
20+
this.sendToRollbar(error);
21+
this.sendToTrackJs(error);
22+
}
23+
24+
// Private methods.
25+
26+
private sendToConsole(error: any) {
27+
if (console && console.group && console.error) {
28+
console.group('Error Logging Service');
29+
console.error(error);
30+
console.groupEnd();
31+
}
32+
}
33+
34+
private sendToRollbar(error: any) {
35+
// Example: Rollbar.error(error);
36+
}
37+
38+
private sendToNewRelic(error: any) {
39+
// Example: newrelic.noticeError(error);
40+
}
41+
42+
private sendToRaygun(error: any) {
43+
// Example: Raygun.send(error);
44+
}
45+
46+
private sendToServer(error: any) {
47+
// ... use http service to send error to your own server.
48+
}
49+
50+
private sendToTrackJs(error: any) {
51+
// Example: trackJs.track(error);
52+
}
53+
}
54+
// #enddocregion class
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* tslint:disable */
2+
// #docregion
3+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4+
5+
import { AppModule } from './app.module';
6+
7+
platformBrowserDynamic().bootstrapModule(AppModule);

public/docs/_examples/cb-exception-handler/ts/example-config.json

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
7+
<title>
8+
Custom Exception Handler Service
9+
</title>
10+
11+
<!-- #docregion style -->
12+
<link rel="stylesheet" type="text/css" href="styles.css">
13+
<link rel="stylesheet" type="text/css" href="sample.css">
14+
<!-- #enddocregion style -->
15+
16+
<!-- Polyfill(s) for older browsers -->
17+
<script src="node_modules/core-js/client/shim.min.js"></script>
18+
19+
<script src="node_modules/zone.js/dist/zone.js"></script>
20+
<script src="node_modules/reflect-metadata/Reflect.js"></script>
21+
<script src="node_modules/systemjs/dist/system.src.js"></script>
22+
23+
<script src="systemjs.config.js"></script>
24+
<script>
25+
System.import('app')
26+
.then(function() {
27+
console.info('System.js loaded your application module.')
28+
})
29+
.catch(function(err) {
30+
console.error(err);
31+
});
32+
</script>
33+
</head>
34+
<body>
35+
36+
<h1>
37+
Custom Exception Handler Service
38+
</h1>
39+
40+
<my-app>Loading app...</my-app>
41+
42+
</body>
43+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"description": "Custom Exception Handler In Angular 2",
3+
"files": [
4+
"!**/*.d.ts",
5+
"!**/*.js",
6+
"!**/*.[1].*"
7+
],
8+
"tags": [ "cookbook" ]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
a {
2+
color: #369 ;
3+
cursor: pointer ;
4+
text-decoration: underline ;
5+
}

public/docs/ts/latest/cookbook/_data.json

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
"intro": "Use relative URLs for component templates and styles."
2222
},
2323

24+
"custom-exception-handler": {
25+
"title": "Custom Exception Handler",
26+
"intro": "Creating a custom exception handler service."
27+
},
28+
2429
"dependency-injection": {
2530
"title": "Dependency Injection",
2631
"intro": "Techniques for Dependency Injection"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
include ../_util-fns
2+
3+
a(id='top')
4+
:marked
5+
When Angular executes the vast majority of our application code, it does so within the
6+
context of a `try-catch` block. And, when it catches errors, it sends them to the
7+
default `ExceptionHandler` which, in turn, logs them to the browser console. This is
8+
great for debugging in our development environment; but, in a production setting, it
9+
would be helpful to track these errors more explicitly. To do this, we can implement a
10+
custom exception handler class.
11+
12+
:marked
13+
**See the [live example](/resources/live-examples/cb-exception-handler/ts/plnkr.html)**.
14+
15+
:marked
16+
First though, let's think about what we might want to do with errors in a production
17+
environment. Perhaps we want to `POST` them, over HTTP, to our server for logging. Or,
18+
maybe we're using a client-side error-tracking service like New Relic, TrackJS, or
19+
Raygun. We can create an `ErrorLoggingService` class that is responsible for routing
20+
errors to different destinations:
21+
22+
+makeExample('cb-exception-handler/ts/app/error-logging.service.ts', 'class', 'app/error-logging.service.ts')(format='.')
23+
24+
:marked
25+
Once we have this error-logging class, we can start piping errors into it through the
26+
use of a custom exception handler. Since the entire Angular platform depends on proper
27+
exception handling, we have to override the `ExceptionHandler` in the providers of our
28+
**application module**. To do this, we have to associate our custom exception handler
29+
class &mdash; `CustomExceptionHandler` &mdash; with the core `ExceptionHandler`
30+
dependency-injection (DI) token. This way, when the Angular internals request an
31+
instance of `ExceptionHandler`, they will be provided with an instance of our
32+
`CustomExceptionHandler`.
33+
34+
+makeExample('cb-exception-handler/ts/app/app.module.ts', '', 'app/app.module.ts (root module for bootstrapping)')(format='.')
35+
36+
:marked
37+
The default `ExceptionHandler` service only has one public method, `.call()`. As such,
38+
our custom exception handler only has to implement the `.call()` method. Inside this
39+
`.call()` method, we're going to log the errors to the console and send them over to
40+
the `ErrorLoggingService` instance.
41+
42+
+makeExample('cb-exception-handler/ts/app/custom-exception-handler.ts', 'class', 'app/custom-exception-handler.ts')(format='.')
43+
44+
:marked
45+
When Angular passes an error into the application exception handler, it doesn't
46+
necessarily pass-in the raw error object &mdash; it may pass-in a wrapped error. This
47+
is _not a documented behavior_; but, if we look at the source code for the core
48+
`ExceptionHandler` class, we can see that it does this. As such, in our custom
49+
exception handler, we attempt to unwrap the root error &mdash; traversing the
50+
`.originalException` object path &mdash; before sending it to the
51+
`ErrorLoggingService`.
52+
53+
:marked
54+
At this point, we've overridden the core `ExceptionHandler` and laid the ground-work
55+
for powerful error logging. Now, we just need to `throw()` an error in our application
56+
and confirm that it's being seen by both the `CustomExceptionHandler` and the
57+
`ErrorLoggingService` classes
58+
59+
+makeExample('cb-exception-handler/ts/app/app.component.ts', 'component', 'app/app.component.ts')(format='.')
60+
61+
:marked
62+
When we run this application and trigger an error, we can see it getting logged to the
63+
console by both the custom exception handler and our error-logging service:
64+
65+
figure.image-display
66+
img(src='/resources/images/cookbooks/custom-exception-handler/custom-exception-handler-animation.gif' alt='Custom exception handler in Angular2.')
67+
68+
:marked
69+
Here's the complete solution:
70+
71+
+makeTabs(
72+
`
73+
cb-exception-handler/ts/app/main.ts,
74+
cb-exception-handler/ts/app/app.module.ts,
75+
cb-exception-handler/ts/app/custom-exception-handler.ts,
76+
cb-exception-handler/ts/app/error-logging.service.ts,
77+
cb-exception-handler/ts/app/app.component.ts
78+
`,
79+
'',
80+
`
81+
main.ts,
82+
app.module.ts,
83+
custom-exception-handler.ts,
84+
error-logging.service.ts,
85+
app.component.ts
86+
`
87+
)
88+
89+
:marked
90+
[Back to top](#top)

0 commit comments

Comments
 (0)