feat(exception_handler): print originalException and originalStack for all exceptions

This commit is contained in:
vsavkin
2015-07-23 18:00:19 -07:00
parent 0a8b3816f7
commit e744409cb9
18 changed files with 259 additions and 120 deletions

View File

@ -64,7 +64,6 @@ import {
} from 'angular2/src/render/dom/dom_renderer';
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import {internalView} from 'angular2/src/core/compiler/view_ref';
import {appComponentRefPromiseToken, appComponentTypeToken} from './application_tokens';
var _rootInjector: Injector;
@ -129,7 +128,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
DirectiveResolver,
Parser,
Lexer,
ExceptionHandler,
bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM)),
bind(XHR).toValue(new XHRImpl()),
ComponentUrlMapper,
UrlResolver,
@ -282,8 +281,7 @@ export function commonBootstrap(
componentInjectableBindings: List<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
BrowserDomAdapter.makeCurrent();
var bootstrapProcess = PromiseWrapper.completer();
var zone = createNgZone(new ExceptionHandler());
var zone = createNgZone(new ExceptionHandler(DOM));
zone.run(() => {
// TODO(rado): prepopulate template cache, so applications with only
// index.html and main.js are possible.

View File

@ -1,7 +1,13 @@
import {Injectable} from 'angular2/di';
import {isPresent, print, BaseException} from 'angular2/src/facade/lang';
import {isPresent, isBlank, print, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, isListLikeIterable} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
class _ArrayLogger {
res: any[] = [];
log(s: any): void { this.res.push(s); }
logGroup(s: any): void { this.res.push(s); }
logGroupEnd(){};
}
/**
* Provides a hook for centralized exception handling.
@ -26,31 +32,91 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
*/
@Injectable()
export class ExceptionHandler {
logError: Function = DOM.logError;
constructor(private logger: any, private rethrowException: boolean = true) {}
call(exception: Object, stackTrace: any = null, reason: string = null) {
var longStackTrace = isListLikeIterable(stackTrace) ?
(<any>stackTrace).join("\n\n-----async gap-----\n") :
stackTrace;
static exceptionToString(exception: any, stackTrace: any = null, reason: string = null): string {
var l = new _ArrayLogger();
var e = new ExceptionHandler(l, false);
e.call(exception, stackTrace, reason);
return l.res.join("\n");
}
this.logError(`${exception}\n\n${longStackTrace}`);
call(exception: any, stackTrace: any = null, reason: string = null): void {
var originalException = this._findOriginalException(exception);
var originalStack = this._findOriginalStack(exception);
var context = this._findContext(exception);
this.logger.logGroup(`EXCEPTION: ${exception}`);
if (isPresent(stackTrace) && isBlank(originalStack)) {
this.logger.log("STACKTRACE:");
this.logger.log(this._longStackTrace(stackTrace))
}
if (isPresent(reason)) {
this.logError(`Reason: ${reason}`);
this.logger.log(`REASON: ${reason}`);
}
if (isPresent(originalException)) {
this.logger.log(`ORIGINAL EXCEPTION: ${originalException}`);
}
if (isPresent(originalStack)) {
this.logger.log("ORIGINAL STACKTRACE:");
this.logger.log(this._longStackTrace(originalStack));
}
var context = this._findContext(exception);
if (isPresent(context)) {
this.logError("Error Context:");
this.logError(context);
this.logger.log("ERROR CONTEXT:");
this.logger.log(context);
}
throw exception;
this.logger.logGroupEnd();
// We rethrow exceptions, so operations like 'bootstrap' will result in an error
// when an exception happens. If we do not rethrow, bootstrap will always succeed.
if (this.rethrowException) throw exception;
}
_longStackTrace(stackTrace: any): any {
return isListLikeIterable(stackTrace) ? (<any>stackTrace).join("\n\n-----async gap-----\n") :
stackTrace;
}
_findContext(exception: any): any {
try {
if (!(exception instanceof BaseException)) return null;
return isPresent(exception.context) ? exception.context :
this._findContext(exception.originalException);
} catch (e) {
// exception.context can throw an exception. if it happens, we ignore the context.
return null;
}
}
_findOriginalException(exception: any): any {
if (!(exception instanceof BaseException)) return null;
return isPresent(exception.context) ? exception.context :
this._findContext(exception.originalException);
var e = exception.originalException;
while (e instanceof BaseException && isPresent(e.originalException)) {
e = e.originalException;
}
return e;
}
_findOriginalStack(exception: any): any {
if (!(exception instanceof BaseException)) return null;
var e = exception;
var stack = exception.originalStack;
while (e instanceof BaseException && isPresent(e.originalException)) {
e = e.originalException;
if (e instanceof BaseException && isPresent(e.originalException)) {
stack = e.originalStack;
}
}
return stack;
}
}

View File

@ -142,6 +142,18 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
window.console.error(error);
}
log(error) {
window.console.log(error);
}
logGroup(error) {
window.console.group(error);
}
logGroupEnd() {
window.console.groupEnd();
}
@override
Map<String, String> get attrToPropMap => const <String, String>{
'innerHtml': 'innerHTML',

View File

@ -61,6 +61,22 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
// TODO(tbosch): move this into a separate environment class once we have it
logError(error) { window.console.error(error); }
log(error) { window.console.log(error); }
logGroup(error) {
if (window.console.group) {
window.console.group(error);
} else {
window.console.log(error);
}
}
logGroupEnd() {
if (window.console.groupEnd) {
window.console.groupEnd();
}
}
get attrToPropMap(): any { return _attrToPropMap; }
query(selector: string): any { return document.querySelector(selector); }

View File

@ -23,6 +23,9 @@ export class DomAdapter {
invoke(el: Element, methodName: string, args: List<any>): any { throw _abstract(); }
logError(error) { throw _abstract(); }
log(error) { throw _abstract(); }
logGroup(error) { throw _abstract(); }
logGroupEnd() { throw _abstract(); }
/**
* Maps attribute names to their corresponding property names for cases

View File

@ -27,6 +27,11 @@ class Html5LibDomAdapter implements DomAdapter {
stderr.writeln('${error}');
}
log(error) { stdout.writeln('${error}'); }
logGroup(error) { stdout.writeln('${error}'); }
logGroupEnd() { }
@override
final attrToPropMap = const {
'innerHtml': 'innerHTML',

View File

@ -47,6 +47,12 @@ export class Parse5DomAdapter extends DomAdapter {
logError(error) { console.error(error); }
log(error) { console.log(error); }
logGroup(error) { console.log(error); }
logGroupEnd() {}
get attrToPropMap() { return _attrToPropMap; }
query(selector) { throw _notImplemented('query'); }

View File

@ -119,7 +119,7 @@ function _getAppBindings() {
DirectiveResolver,
Parser,
Lexer,
ExceptionHandler,
bind(ExceptionHandler).toValue(new ExceptionHandler(DOM)),
bind(LocationStrategy).toClass(MockLocationStrategy),
bind(XHR).toClass(MockXHR),
ComponentUrlMapper,

View File

@ -22,6 +22,7 @@ import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'package:angular2/src/di/binding.dart' show bind;
import 'package:angular2/src/di/injector.dart' show Injector;
import 'package:angular2/src/core/exception_handler.dart' show ExceptionHandler;
import 'package:angular2/src/facade/collection.dart' show StringMapWrapper;
import 'test_injector.dart';
@ -77,13 +78,27 @@ Expect expect(actual, [matcher]) {
const _u = const Object();
expectErrorMessage(actual, expectedMessage) {
expect(ExceptionHandler.exceptionToString(actual)).toContain(expectedMessage);
}
expectException(Function actual, expectedMessage) {
try {
actual();
} catch (e, s) {
expectErrorMessage(e, expectedMessage);
}
}
class Expect extends gns.Expect {
Expect(actual) : super(actual);
NotExpect get not => new NotExpect(actual);
void toEqual(expected) => toHaveSameProps(expected);
void toContainError(message) => expectErrorMessage(this.actual, message);
void toThrowError([message = ""]) => toThrowWith(message: message);
void toThrowErrorWith(message) => expectException(this.actual, message);
void toBePromise() => gns.guinness.matchers.toBeTrue(actual is Future);
void toImplement(expected) => toBeA(expected);
void toBeNaN() => gns.guinness.matchers.toBeTrue(double.NAN.compareTo(actual) == 0);

View File

@ -6,6 +6,7 @@ import {global} from 'angular2/src/facade/lang';
import {NgZoneZone} from 'angular2/src/core/zone/ng_zone';
import {bind} from 'angular2/di';
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
@ -24,6 +25,8 @@ export interface NgMatchers extends jasmine.Matchers {
toBeAnInstanceOf(expected: any): boolean;
toHaveText(expected: any): boolean;
toImplement(expected: any): boolean;
toContainError(expected: any): boolean;
toThrowErrorWith(expectedMessage: any): boolean;
not: NgMatchers;
}
@ -240,6 +243,38 @@ _global.beforeEach(function() {
};
},
toContainError: function() {
return {
compare: function(actual, expectedText) {
var errorMessage = ExceptionHandler.exceptionToString(actual);
return {
pass: errorMessage.indexOf(expectedText) > -1,
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
};
}
};
},
toThrowErrorWith: function() {
return {
compare: function(actual, expectedText) {
try {
actual();
return {
pass: false,
get message() { return "Was expected to throw, but did not throw"; }
};
} catch (e) {
var errorMessage = ExceptionHandler.exceptionToString(e);
return {
pass: errorMessage.indexOf(expectedText) > -1,
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
};
}
}
};
},
toImplement: function() {
return {
compare: function(actualObject, expectedInterface) {

View File

@ -31,6 +31,7 @@ import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {Injectable} from 'angular2/di';
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
import {DOM} from 'angular2/src/dom/dom_adapter';
/**
* Creates a zone, sets up the DI bindings
@ -38,7 +39,7 @@ import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
*/
export function bootstrapUICommon(bus: MessageBus) {
BrowserDomAdapter.makeCurrent();
var zone = createNgZone(new ExceptionHandler());
var zone = createNgZone(new ExceptionHandler(DOM));
zone.run(() => {
var injector = createInjector(zone);
var webWorkerMain = injector.get(WebWorkerMain);

View File

@ -62,13 +62,18 @@ import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_pr
import {
RenderViewWithFragmentsStore
} from 'angular2/src/web-workers/shared/render_view_with_fragments_store';
import {WorkerExceptionHandler} from 'angular2/src/web-workers/worker/exception_handler';
var _rootInjector: Injector;
// Contains everything that is safe to share between applications.
var _rootBindings = [bind(Reflector).toValue(reflector)];
class PrintLogger {
log = print;
logGroup = print;
logGroupEnd() {}
}
function _injectorBindings(appComponentType, bus: WorkerMessageBus,
initData: StringMap<string, any>): List<Type | Binding | List<any>> {
var bestChangeDetection: Type = DynamicChangeDetection;
@ -118,8 +123,7 @@ function _injectorBindings(appComponentType, bus: WorkerMessageBus,
DirectiveResolver,
Parser,
Lexer,
WorkerExceptionHandler,
bind(ExceptionHandler).toAlias(WorkerExceptionHandler),
bind(ExceptionHandler).toFactory(() => {new ExceptionHandler(new PrintLogger())}),
bind(XHR).toValue(new XHRImpl()),
ComponentUrlMapper,
UrlResolver,
@ -135,7 +139,7 @@ export function bootstrapWebworkerCommon(
componentInjectableBindings: List<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
var bootstrapProcess = PromiseWrapper.completer();
var zone = createNgZone(new WorkerExceptionHandler());
var zone = createNgZone(new ExceptionHandler(new PrintLogger()));
zone.run(() => {
// TODO(rado): prepopulate template cache, so applications with only
// index.html and main.js are possible.

View File

@ -1,35 +0,0 @@
import {isPresent, print, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, isListLikeIterable} from 'angular2/src/facade/collection';
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {Injectable} from 'angular2/di';
@Injectable()
export class WorkerExceptionHandler implements ExceptionHandler {
logError: Function = print;
call(exception: Object, stackTrace: any = null, reason: string = null) {
var longStackTrace = isListLikeIterable(stackTrace) ?
(<any>stackTrace).join("\n\n-----async gap-----\n") :
stackTrace;
this.logError(`${exception}\n\n${longStackTrace}`);
if (isPresent(reason)) {
this.logError(`Reason: ${reason}`);
}
var context = this._findContext(exception);
if (isPresent(context)) {
this.logError("Error Context:");
this.logError(context);
}
throw exception;
}
_findContext(exception: any): any {
if (!(exception instanceof BaseException)) return null;
return isPresent(exception.context) ? exception.context :
this._findContext(exception.originalException);
}
}