From ceb09fa75c7ffc3867a0122b194fac020ddfc529 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Wed, 4 Oct 2023 08:15:33 -0500 Subject: [PATCH] Reworked how stack traces are rendered --- .../ClientApp/package-lock.json | 20 -- src/Exceptionless.Web/ClientApp/package.json | 2 - .../components/events/SimpleStackTrace.svelte | 122 ++-------- .../lib/components/events/StackTrace.svelte | 209 ++---------------- .../components/events/StackTraceHeader.svelte | 19 ++ .../components/events/views/Overview.svelte | 7 +- .../src/lib/helpers/persistent-event.ts | 155 ++++++++++++- 7 files changed, 217 insertions(+), 317 deletions(-) create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/components/events/StackTraceHeader.svelte diff --git a/src/Exceptionless.Web/ClientApp/package-lock.json b/src/Exceptionless.Web/ClientApp/package-lock.json index 86c1892bda..dadf341854 100644 --- a/src/Exceptionless.Web/ClientApp/package-lock.json +++ b/src/Exceptionless.Web/ClientApp/package-lock.json @@ -11,11 +11,9 @@ "@exceptionless/browser": "^3.0.4", "@iconify-json/mdi": "^1.1.54", "@tanstack/svelte-table": "^8.10.3", - "@types/dompurify": "^3.0.3", "@web3-storage/parse-link-header": "^3.1.0", "class-validator": "^0.14.0", "daisyui": "^3.8.3", - "dompurify": "^3.0.6", "oidc-client-ts": "^2.3.0", "svelte-local-storage-store": "^0.6.3", "svelte-time": "^0.8.0", @@ -1022,14 +1020,6 @@ "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==", "dev": true }, - "node_modules/@types/dompurify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.3.tgz", - "integrity": "sha512-odiGr/9/qMqjcBOe5UhcNLOFHSYmKFOyr+bJ/Xu3Qp4k1pNPAlNLUVNNLcLfjQI7+W7ObX58EdD3H+3p3voOvA==", - "dependencies": { - "@types/trusted-types": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", @@ -1065,11 +1055,6 @@ "integrity": "sha512-7yQiX6MWSFSvc/1wW5smJMZTZ4fHOd+hqLr3qr/HONDxHEa2bnYAsOcGBOEqFIjd4yetwMOdEDdeW+udRAQnHA==", "dev": true }, - "node_modules/@types/trusted-types": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.4.tgz", - "integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==" - }, "node_modules/@types/validator": { "version": "13.11.1", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.1.tgz", @@ -2161,11 +2146,6 @@ "node": ">=6.0.0" } }, - "node_modules/dompurify": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.6.tgz", - "integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w==" - }, "node_modules/electron-to-chromium": { "version": "1.4.495", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.495.tgz", diff --git a/src/Exceptionless.Web/ClientApp/package.json b/src/Exceptionless.Web/ClientApp/package.json index 351a300028..699eae9ef6 100644 --- a/src/Exceptionless.Web/ClientApp/package.json +++ b/src/Exceptionless.Web/ClientApp/package.json @@ -49,11 +49,9 @@ "@exceptionless/browser": "^3.0.4", "@iconify-json/mdi": "^1.1.54", "@tanstack/svelte-table": "^8.10.3", - "@types/dompurify": "^3.0.3", "@web3-storage/parse-link-header": "^3.1.0", "class-validator": "^0.14.0", "daisyui": "^3.8.3", - "dompurify": "^3.0.6", "oidc-client-ts": "^2.3.0", "svelte-local-storage-store": "^0.6.3", "svelte-time": "^0.8.0", diff --git a/src/Exceptionless.Web/ClientApp/src/lib/components/events/SimpleStackTrace.svelte b/src/Exceptionless.Web/ClientApp/src/lib/components/events/SimpleStackTrace.svelte index 6f9d64712c..b272c37d6c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/components/events/SimpleStackTrace.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/components/events/SimpleStackTrace.svelte @@ -1,112 +1,30 @@
{@html stackTrace}
+ class="max-h-[500px] overflow-y-scroll overflow-x-scroll break-normal resize-y whitespace-pre tab-size-2"> + + + {#each errors.reverse() as error, index} + {#if error.stack_trace} +
{cleanStackTrace( + error.stack_trace + )}
+ {#if index < errors.length - 1} +
--- End of inner error stack trace ---
+ {/if} + {/if} + {/each} +
+ diff --git a/src/Exceptionless.Web/ClientApp/src/lib/components/events/StackTrace.svelte b/src/Exceptionless.Web/ClientApp/src/lib/components/events/StackTrace.svelte index 99091f3a21..129c9a0563 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/components/events/StackTrace.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/components/events/StackTrace.svelte @@ -1,198 +1,27 @@
{@html stackTrace}
+ > + {#each errors.reverse() as error, index} + {#if error.stack_trace} +
+ {#each error.stack_trace as frame} + {getStackFrame(frame)} + {/each} + {#if index < errors.length - 1} +
--- End of inner exception stack trace ---
+ {/if} +
+ {/if} + {/each} + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/components/events/StackTraceHeader.svelte b/src/Exceptionless.Web/ClientApp/src/lib/components/events/StackTraceHeader.svelte new file mode 100644 index 0000000000..42e9ed71a9 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/components/events/StackTraceHeader.svelte @@ -0,0 +1,19 @@ + + +{#each errors as error, index} + + {#if index > 0} + ---> + {/if} + {#if error.type} + {error.type}: + {/if} + {#if error.message} + {error.message} + {/if} + +{/each} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/components/events/views/Overview.svelte b/src/Exceptionless.Web/ClientApp/src/lib/components/events/views/Overview.svelte index 5c5a027f02..63b35caf37 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/components/events/views/Overview.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/components/events/views/Overview.svelte @@ -199,7 +199,10 @@

Stack Trace

- - + {#if event.data?.['@error']} + + {:else} + + {/if}
{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/helpers/persistent-event.ts b/src/Exceptionless.Web/ClientApp/src/lib/helpers/persistent-event.ts index a7161ae431..00e8109820 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/helpers/persistent-event.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/helpers/persistent-event.ts @@ -1,5 +1,10 @@ import type { PersistentEvent } from '$lib/models/api'; -import type { ErrorInfo, SimpleErrorInfo } from '$lib/models/client-data'; +import type { + ErrorInfo, + ParameterInfo, + SimpleErrorInfo, + StackFrameInfo +} from '$lib/models/client-data'; import { buildUrl } from './url'; export function getLocation(event: PersistentEvent) { @@ -80,3 +85,151 @@ export function getTargetInfoMessage(error: ErrorInfo) { const target = getTargetInfo(error); return target?.Message; } + +function getParameter(parameter: ParameterInfo) { + let result = ''; + + const parts = []; + if (parameter.type_namespace) { + parts.push(parameter.type_namespace); + } + + if (parameter.type) { + parts.push(parameter.type); + } + + result += parts.join('.').replace('+', '.'); + + if (parameter.generic_arguments && parameter.generic_arguments.length > 0) { + result += `[${parameter.generic_arguments.join(',')}]`; + } + + if (parameter.name) { + result += ` ${parameter.name}`; + } + + return result; +} + +function getParameters(parameters?: ParameterInfo[]) { + let result = '('; + + parameters?.forEach((parameter, index) => { + if (index > 0) { + result += ', '; + } + + result += getParameter(parameter); + }); + + return result + ')'; +} + +export function getStackFrame(frame: StackFrameInfo) { + if (!frame) { + return '\r\n'; + } + + const typeNameParts = []; + if (frame.declaring_namespace) { + typeNameParts.push(frame.declaring_namespace); + } + + if (frame.declaring_type) { + typeNameParts.push(frame.declaring_type); + } + + typeNameParts.push(frame.name || ''); + + let result = `at ${typeNameParts.join('.').replace('+', '.')}`; + + if (frame.generic_arguments && frame.generic_arguments.length > 0) { + result += `[${frame.generic_arguments.join(',')}]`; + } + + result += getParameters(frame.parameters); + if (frame.data && (frame.data.ILOffset || frame.data.NativeOffset)) { + result += ` at offset ${frame.data.ILOffset || frame.data.NativeOffset}`; + } + + if (frame.file_name) { + result += ` in ${frame.file_name}`; + if (frame.line_number) { + result += `:line ${frame.line_number}`; + } + + if (frame.column) { + result += `:col ${frame.column}`; + } + } + + return `${result}\r\n`; +} + +function getStackTraceHeader(errors: T[]) { + let header = ''; + errors.forEach((error, index) => { + if (index > 0) { + header += ' ---> '; + } + + const hasType = !!error.type; + if (hasType) { + header += `${error.type}: `; + } + + if (error.message) { + header += error.message; + } + + if (hasType) { + header += '\r\n'; + } + }); + + return header; +} + +export function getErrorInfoStackTrace(error: ErrorInfo) { + function buildStackFrames(errors: ErrorInfo[]) { + let frames = ''; + errors.forEach((error, index) => { + const stackTrace = error.stack_trace; + if (stackTrace) { + stackTrace.forEach((trace) => { + frames += getStackFrame(trace); + }); + + if (index < errors.length - 1) { + frames += '--- End of inner exception stack trace ---'; + } + } + }); + + return frames; + } + + const errors = getErrors(error); + return getStackTraceHeader(errors) + buildStackFrames(errors.reverse()); +} + +export function getSimpleErrorInfoStackTrace(error: SimpleErrorInfo) { + function buildStackFrames(errors: SimpleErrorInfo[]) { + let frames = ''; + errors.forEach((error, index) => { + const stackTrace = error.stack_trace; + if (stackTrace) { + frames += stackTrace.replace(' ', ''); + + if (index < errors.length - 1) { + frames += '--- End of inner error stack trace ---'; + } + } + }); + + return frames; + } + + const errors = getErrors(error); + return getStackTraceHeader(errors) + buildStackFrames(errors.reverse()); +}