X Tutup
Skip to content

Commit e744409

Browse files
committed
feat(exception_handler): print originalException and originalStack for all exceptions
1 parent 0a8b381 commit e744409

18 files changed

+259
-120
lines changed

modules/angular2/src/core/application_common.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ import {
6464
} from 'angular2/src/render/dom/dom_renderer';
6565
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
6666
import {internalView} from 'angular2/src/core/compiler/view_ref';
67-
6867
import {appComponentRefPromiseToken, appComponentTypeToken} from './application_tokens';
6968

7069
var _rootInjector: Injector;
@@ -129,7 +128,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
129128
DirectiveResolver,
130129
Parser,
131130
Lexer,
132-
ExceptionHandler,
131+
bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM)),
133132
bind(XHR).toValue(new XHRImpl()),
134133
ComponentUrlMapper,
135134
UrlResolver,
@@ -282,8 +281,7 @@ export function commonBootstrap(
282281
componentInjectableBindings: List<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
283282
BrowserDomAdapter.makeCurrent();
284283
var bootstrapProcess = PromiseWrapper.completer();
285-
286-
var zone = createNgZone(new ExceptionHandler());
284+
var zone = createNgZone(new ExceptionHandler(DOM));
287285
zone.run(() => {
288286
// TODO(rado): prepopulate template cache, so applications with only
289287
// index.html and main.js are possible.

modules/angular2/src/core/exception_handler.ts

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import {Injectable} from 'angular2/di';
2-
import {isPresent, print, BaseException} from 'angular2/src/facade/lang';
2+
import {isPresent, isBlank, print, BaseException} from 'angular2/src/facade/lang';
33
import {ListWrapper, isListLikeIterable} from 'angular2/src/facade/collection';
4-
import {DOM} from 'angular2/src/dom/dom_adapter';
4+
5+
class _ArrayLogger {
6+
res: any[] = [];
7+
log(s: any): void { this.res.push(s); }
8+
logGroup(s: any): void { this.res.push(s); }
9+
logGroupEnd(){};
10+
}
511

612
/**
713
* Provides a hook for centralized exception handling.
@@ -26,31 +32,91 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
2632
*/
2733
@Injectable()
2834
export class ExceptionHandler {
29-
logError: Function = DOM.logError;
35+
constructor(private logger: any, private rethrowException: boolean = true) {}
3036

31-
call(exception: Object, stackTrace: any = null, reason: string = null) {
32-
var longStackTrace = isListLikeIterable(stackTrace) ?
33-
(<any>stackTrace).join("\n\n-----async gap-----\n") :
34-
stackTrace;
37+
static exceptionToString(exception: any, stackTrace: any = null, reason: string = null): string {
38+
var l = new _ArrayLogger();
39+
var e = new ExceptionHandler(l, false);
40+
e.call(exception, stackTrace, reason);
41+
return l.res.join("\n");
42+
}
3543

36-
this.logError(`${exception}\n\n${longStackTrace}`);
44+
call(exception: any, stackTrace: any = null, reason: string = null): void {
45+
var originalException = this._findOriginalException(exception);
46+
var originalStack = this._findOriginalStack(exception);
47+
var context = this._findContext(exception);
48+
49+
this.logger.logGroup(`EXCEPTION: ${exception}`);
50+
51+
if (isPresent(stackTrace) && isBlank(originalStack)) {
52+
this.logger.log("STACKTRACE:");
53+
this.logger.log(this._longStackTrace(stackTrace))
54+
}
3755

3856
if (isPresent(reason)) {
39-
this.logError(`Reason: ${reason}`);
57+
this.logger.log(`REASON: ${reason}`);
58+
}
59+
60+
if (isPresent(originalException)) {
61+
this.logger.log(`ORIGINAL EXCEPTION: ${originalException}`);
62+
}
63+
64+
if (isPresent(originalStack)) {
65+
this.logger.log("ORIGINAL STACKTRACE:");
66+
this.logger.log(this._longStackTrace(originalStack));
4067
}
4168

42-
var context = this._findContext(exception);
4369
if (isPresent(context)) {
44-
this.logError("Error Context:");
45-
this.logError(context);
70+
this.logger.log("ERROR CONTEXT:");
71+
this.logger.log(context);
4672
}
4773

48-
throw exception;
74+
this.logger.logGroupEnd();
75+
76+
// We rethrow exceptions, so operations like 'bootstrap' will result in an error
77+
// when an exception happens. If we do not rethrow, bootstrap will always succeed.
78+
if (this.rethrowException) throw exception;
79+
}
80+
81+
_longStackTrace(stackTrace: any): any {
82+
return isListLikeIterable(stackTrace) ? (<any>stackTrace).join("\n\n-----async gap-----\n") :
83+
stackTrace;
4984
}
5085

5186
_findContext(exception: any): any {
87+
try {
88+
if (!(exception instanceof BaseException)) return null;
89+
return isPresent(exception.context) ? exception.context :
90+
this._findContext(exception.originalException);
91+
} catch (e) {
92+
// exception.context can throw an exception. if it happens, we ignore the context.
93+
return null;
94+
}
95+
}
96+
97+
_findOriginalException(exception: any): any {
5298
if (!(exception instanceof BaseException)) return null;
53-
return isPresent(exception.context) ? exception.context :
54-
this._findContext(exception.originalException);
99+
100+
var e = exception.originalException;
101+
while (e instanceof BaseException && isPresent(e.originalException)) {
102+
e = e.originalException;
103+
}
104+
105+
return e;
106+
}
107+
108+
_findOriginalStack(exception: any): any {
109+
if (!(exception instanceof BaseException)) return null;
110+
111+
var e = exception;
112+
var stack = exception.originalStack;
113+
while (e instanceof BaseException && isPresent(e.originalException)) {
114+
e = e.originalException;
115+
if (e instanceof BaseException && isPresent(e.originalException)) {
116+
stack = e.originalStack;
117+
}
118+
}
119+
120+
return stack;
55121
}
56122
}

modules/angular2/src/dom/browser_adapter.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
142142
window.console.error(error);
143143
}
144144

145+
log(error) {
146+
window.console.log(error);
147+
}
148+
149+
logGroup(error) {
150+
window.console.group(error);
151+
}
152+
153+
logGroupEnd() {
154+
window.console.groupEnd();
155+
}
156+
145157
@override
146158
Map<String, String> get attrToPropMap => const <String, String>{
147159
'innerHtml': 'innerHTML',

modules/angular2/src/dom/browser_adapter.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
6161
// TODO(tbosch): move this into a separate environment class once we have it
6262
logError(error) { window.console.error(error); }
6363

64+
log(error) { window.console.log(error); }
65+
66+
logGroup(error) {
67+
if (window.console.group) {
68+
window.console.group(error);
69+
} else {
70+
window.console.log(error);
71+
}
72+
}
73+
74+
logGroupEnd() {
75+
if (window.console.groupEnd) {
76+
window.console.groupEnd();
77+
}
78+
}
79+
6480
get attrToPropMap(): any { return _attrToPropMap; }
6581

6682
query(selector: string): any { return document.querySelector(selector); }

modules/angular2/src/dom/dom_adapter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export class DomAdapter {
2323
invoke(el: Element, methodName: string, args: List<any>): any { throw _abstract(); }
2424

2525
logError(error) { throw _abstract(); }
26+
log(error) { throw _abstract(); }
27+
logGroup(error) { throw _abstract(); }
28+
logGroupEnd() { throw _abstract(); }
2629

2730
/**
2831
* Maps attribute names to their corresponding property names for cases

modules/angular2/src/dom/html_adapter.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ class Html5LibDomAdapter implements DomAdapter {
2727
stderr.writeln('${error}');
2828
}
2929

30+
log(error) { stdout.writeln('${error}'); }
31+
logGroup(error) { stdout.writeln('${error}'); }
32+
logGroupEnd() { }
33+
34+
3035
@override
3136
final attrToPropMap = const {
3237
'innerHtml': 'innerHTML',

modules/angular2/src/dom/parse5_adapter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ export class Parse5DomAdapter extends DomAdapter {
4747

4848
logError(error) { console.error(error); }
4949

50+
log(error) { console.log(error); }
51+
52+
logGroup(error) { console.log(error); }
53+
54+
logGroupEnd() {}
55+
5056
get attrToPropMap() { return _attrToPropMap; }
5157

5258
query(selector) { throw _notImplemented('query'); }

modules/angular2/src/test_lib/test_injector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ function _getAppBindings() {
119119
DirectiveResolver,
120120
Parser,
121121
Lexer,
122-
ExceptionHandler,
122+
bind(ExceptionHandler).toValue(new ExceptionHandler(DOM)),
123123
bind(LocationStrategy).toClass(MockLocationStrategy),
124124
bind(XHR).toClass(MockXHR),
125125
ComponentUrlMapper,

modules/angular2/src/test_lib/test_lib.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'package:angular2/src/reflection/reflection_capabilities.dart';
2222

2323
import 'package:angular2/src/di/binding.dart' show bind;
2424
import 'package:angular2/src/di/injector.dart' show Injector;
25+
import 'package:angular2/src/core/exception_handler.dart' show ExceptionHandler;
2526
import 'package:angular2/src/facade/collection.dart' show StringMapWrapper;
2627

2728
import 'test_injector.dart';
@@ -77,13 +78,27 @@ Expect expect(actual, [matcher]) {
7778

7879
const _u = const Object();
7980

81+
expectErrorMessage(actual, expectedMessage) {
82+
expect(ExceptionHandler.exceptionToString(actual)).toContain(expectedMessage);
83+
}
84+
85+
expectException(Function actual, expectedMessage) {
86+
try {
87+
actual();
88+
} catch (e, s) {
89+
expectErrorMessage(e, expectedMessage);
90+
}
91+
}
92+
8093
class Expect extends gns.Expect {
8194
Expect(actual) : super(actual);
8295

8396
NotExpect get not => new NotExpect(actual);
8497

8598
void toEqual(expected) => toHaveSameProps(expected);
99+
void toContainError(message) => expectErrorMessage(this.actual, message);
86100
void toThrowError([message = ""]) => toThrowWith(message: message);
101+
void toThrowErrorWith(message) => expectException(this.actual, message);
87102
void toBePromise() => gns.guinness.matchers.toBeTrue(actual is Future);
88103
void toImplement(expected) => toBeA(expected);
89104
void toBeNaN() => gns.guinness.matchers.toBeTrue(double.NAN.compareTo(actual) == 0);

modules/angular2/src/test_lib/test_lib.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {global} from 'angular2/src/facade/lang';
66
import {NgZoneZone} from 'angular2/src/core/zone/ng_zone';
77

88
import {bind} from 'angular2/di';
9+
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
910

1011
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
1112

@@ -24,6 +25,8 @@ export interface NgMatchers extends jasmine.Matchers {
2425
toBeAnInstanceOf(expected: any): boolean;
2526
toHaveText(expected: any): boolean;
2627
toImplement(expected: any): boolean;
28+
toContainError(expected: any): boolean;
29+
toThrowErrorWith(expectedMessage: any): boolean;
2730
not: NgMatchers;
2831
}
2932

@@ -240,6 +243,38 @@ _global.beforeEach(function() {
240243
};
241244
},
242245

246+
toContainError: function() {
247+
return {
248+
compare: function(actual, expectedText) {
249+
var errorMessage = ExceptionHandler.exceptionToString(actual);
250+
return {
251+
pass: errorMessage.indexOf(expectedText) > -1,
252+
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
253+
};
254+
}
255+
};
256+
},
257+
258+
toThrowErrorWith: function() {
259+
return {
260+
compare: function(actual, expectedText) {
261+
try {
262+
actual();
263+
return {
264+
pass: false,
265+
get message() { return "Was expected to throw, but did not throw"; }
266+
};
267+
} catch (e) {
268+
var errorMessage = ExceptionHandler.exceptionToString(e);
269+
return {
270+
pass: errorMessage.indexOf(expectedText) > -1,
271+
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
272+
};
273+
}
274+
}
275+
};
276+
},
277+
243278
toImplement: function() {
244279
return {
245280
compare: function(actualObject, expectedInterface) {

0 commit comments

Comments
 (0)
X Tutup