From a90e49b60450244c571b42b3534657afab6f34d0 Mon Sep 17 00:00:00 2001 From: Max Shekhovtcov Date: Mon, 1 Jun 2015 16:52:16 -0700 Subject: [PATCH] Showing details in case of CORS exception --- .../CheckinTests/Util.tests.ts | 18 ++++++-- .../JavaScriptSDK/Telemetry/Exception.ts | 35 +++++++++++++- JavaScript/JavaScriptSDK/Util.ts | 12 +++++ JavaScript/JavaScriptSDK/appInsights.ts | 46 ++++++++++++++----- 4 files changed, 94 insertions(+), 17 deletions(-) diff --git a/JavaScript/JavaScriptSDK.Tests/CheckinTests/Util.tests.ts b/JavaScript/JavaScriptSDK.Tests/CheckinTests/Util.tests.ts index 8e31c7d7..d1a8d93f 100644 --- a/JavaScript/JavaScriptSDK.Tests/CheckinTests/Util.tests.ts +++ b/JavaScript/JavaScriptSDK.Tests/CheckinTests/Util.tests.ts @@ -34,14 +34,14 @@ class UtilTests extends TestClass { // mock cookies ((document) => { var cookies = {}; - document.__defineGetter__('cookie', () => { + document.__defineGetter__('cookie',() => { var output = []; for (var cookieName in cookies) { output.push(cookieName + "=" + cookies[cookieName]); } return output.join(";"); }); - document.__defineSetter__('cookie', (s) => { + document.__defineSetter__('cookie',(s) => { var indexOfSeparator = s.indexOf("="); var key = s.substr(0, indexOfSeparator); var value = s.substring(indexOfSeparator + 1); @@ -93,7 +93,7 @@ class UtilTests extends TestClass { this.testCase({ name: "UtilTests: new GUID", test: () => { - sinon.stub(Math, "random", () => 0); + sinon.stub(Math, "random",() => 0); var expected = "00000000-0000-4000-8000-000000000000"; var actual = Microsoft.ApplicationInsights.Util.newGuid(); Assert.equal(expected, actual, "expected guid was generated"); @@ -163,7 +163,7 @@ class UtilTests extends TestClass { Assert.ok(Microsoft.ApplicationInsights.Util.stringToBoolOrDefault(null) === false); Assert.ok(Microsoft.ApplicationInsights.Util.stringToBoolOrDefault("") === false); Assert.ok(Microsoft.ApplicationInsights.Util.stringToBoolOrDefault("asdf") === false); - Assert.ok(Microsoft.ApplicationInsights.Util.stringToBoolOrDefault(0) === false); + Assert.ok(Microsoft.ApplicationInsights.Util.stringToBoolOrDefault(0) === false); Assert.ok(Microsoft.ApplicationInsights.Util.stringToBoolOrDefault({ asfd: "sdf" }) === false); Assert.ok(Microsoft.ApplicationInsights.Util.stringToBoolOrDefault(new Object()) === false); @@ -171,6 +171,16 @@ class UtilTests extends TestClass { Assert.ok(Microsoft.ApplicationInsights.Util.stringToBoolOrDefault("TrUe") === true); } }); + + this.testCase({ + name: "UtilTests: isCrossOriginError", + test: () => { + Assert.ok(Microsoft.ApplicationInsights.Util.isCrossOriginError("Script error.", "", 0, 0, null) === true); + + Assert.ok(Microsoft.ApplicationInsights.Util.isCrossOriginError("Script error.", "http://microsoft.com", 0, 0, null) + === false); + } + }); } } new UtilTests().registerTests(); \ No newline at end of file diff --git a/JavaScript/JavaScriptSDK/Telemetry/Exception.ts b/JavaScript/JavaScriptSDK/Telemetry/Exception.ts index 59961d24..df0622b3 100644 --- a/JavaScript/JavaScriptSDK/Telemetry/Exception.ts +++ b/JavaScript/JavaScriptSDK/Telemetry/Exception.ts @@ -32,6 +32,39 @@ module Microsoft.ApplicationInsights.Telemetry { this.handledAt = handledAt || "unhandled"; this.exceptions = [new _ExceptionDetails(exception)]; } + + + /** + * Creates a simple exception with 1 stack frame. Useful for manual constracting of exception. + */ + public static CreateSimpleException(message: string, typeName: string, assembly: string, fileName: string, + details: string, line: number, handledAt?: string): Telemetry.Exception { + + // We can't override constructors, so throwing a fake error to use existing constructor and override all fields after that. + var exceptionTelemetry; + try { + throw new Error(); + } catch (e) { + exceptionTelemetry = new Telemetry.Exception(e); + } + + var stack = exceptionTelemetry.exceptions[0].parsedStack[0]; + stack.assembly = assembly; + stack.fileName = fileName; + stack.level = 0; + stack.line = line; + stack.method = "unknown"; + + var exception = exceptionTelemetry.exceptions[0]; + exception.hasFullStack = true; + exception.message = message; + exception.parsedStack = null; + exception.stack = details; + exception.typeName = typeName; + exceptionTelemetry.handledAt = handledAt || "unhandled"; + + return exceptionTelemetry; + } } class _ExceptionDetails extends AI.ExceptionDetails implements ISerializable { @@ -45,7 +78,7 @@ module Microsoft.ApplicationInsights.Telemetry { stack: false, parsedStack: [] }; - + constructor(exception: Error) { super(); this.typeName = Common.DataSanitizer.sanitizeString(exception.name); diff --git a/JavaScript/JavaScriptSDK/Util.ts b/JavaScript/JavaScriptSDK/Util.ts index f8b74d74..ee2eac5a 100644 --- a/JavaScript/JavaScriptSDK/Util.ts +++ b/JavaScript/JavaScriptSDK/Util.ts @@ -135,5 +135,17 @@ return hour + ":" + min + ":" + sec + "." + ms; } + + /** + * Checks if error has no meaningful data inside. Ususally such errors are received by window.onerror when error + * happens in a script from other domain (cross origin, CORS). + */ + public static isCrossOriginError(message: string, url: string, lineNumber: number, columnNumber: number, error: Error): boolean { + return (message == "Script error." || message == "Script error") + && url == "" + && lineNumber == 0 + && columnNumber == 0 + && error == null; + } } } \ No newline at end of file diff --git a/JavaScript/JavaScriptSDK/appInsights.ts b/JavaScript/JavaScriptSDK/appInsights.ts index bed4bb75..2160fa6e 100644 --- a/JavaScript/JavaScriptSDK/appInsights.ts +++ b/JavaScript/JavaScriptSDK/appInsights.ts @@ -65,7 +65,7 @@ module Microsoft.ApplicationInsights { emitLineDelimitedJson: () => this.config.emitLineDelimitedJson, maxBatchSizeInBytes: () => this.config.maxBatchSizeInBytes, maxBatchInterval: () => this.config.maxBatchInterval, - disableTelemetry: () => this.config.disableTelemetry + disableTelemetry: () => this.config.disableTelemetry } this.context = new ApplicationInsights.TelemetryContext(configGetters); @@ -311,6 +311,21 @@ module Microsoft.ApplicationInsights { } } + /** + * In case of CORS exceptions - construct an exception manually. + * See this for more info: http://stackoverflow.com/questions/5913978/cryptic-script-error-reported-in-javascript-in-chrome-and-firefox + */ + private SendCORSException() { + var exceptionData = Microsoft.ApplicationInsights.Telemetry.Exception.CreateSimpleException( + "Script error.", "Error", "unknown", "unknown", + "There is no details for this exception because it violated browser’s same-origin policy. For cross-domain error reporting you can use crossorigtin attribute together with appropriate CORS HTTP headers. For more information please see http://www.w3.org/TR/cors/", + 0, null); + + var data = new ApplicationInsights.Telemetry.Common.Data(Telemetry.Exception.dataType, exceptionData); + var envelope = new Telemetry.Common.Envelope(data, Telemetry.Exception.envelopeType); + this.context.track(envelope); + } + /** * The custom error handler for Application Insights * @param {string} message - The error message @@ -321,21 +336,28 @@ module Microsoft.ApplicationInsights { */ public _onerror(message: string, url: string, lineNumber: number, columnNumber: number, error: Error) { try { - if (!Util.isError(error)) { - // ensure that we have an error object (browser may not pass an error i.e safari) - try { - throw new Error(message); - } catch (exception) { - error = exception; - if (!error["stack"]) { - error["stack"] = "@" + url + ":" + lineNumber + ":" + (columnNumber || 0); + if (Util.isCrossOriginError(message, url, lineNumber, columnNumber, error)) { + this.SendCORSException(); + } else { + if (!Util.isError(error)) { + // ensure that we have an error object (browser may not pass an error i.e safari) + try { + throw new Error(message); + } catch (exception) { + error = exception; + if (!error["stack"]) { + error["stack"] = "@" + url + ":" + lineNumber + ":" + (columnNumber || 0); + } } + + this.trackException(error); } } - - this.trackException(error); } catch (exception) { - _InternalLogging.throwInternalNonUserActionable(LoggingSeverity.CRITICAL, "_onerror threw an exception: " + JSON.stringify(exception) + " while logging error: " + error.name + ", " + error.message); + var errorString = + error ? (error.name + ", " + error.message) : "null"; + + _InternalLogging.throwInternalNonUserActionable(LoggingSeverity.CRITICAL, "_onerror threw an exception: " + JSON.stringify(exception) + " while logging error: " + errorString); } } }