Skip to content

Commit 4955414

Browse files
authored
Merge pull request #2408 from Particular/john/stacktrace
Adding new stacktrace visualiser
2 parents 629c47d + afcfb34 commit 4955414

File tree

2 files changed

+244
-2
lines changed

2 files changed

+244
-2
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<script lang="ts" setup>
2+
import { ref, watch } from "vue";
3+
4+
// Define TypeScript interfaces for settings and supported languages
5+
interface NetStackOptions {
6+
frame?: string;
7+
type?: string;
8+
method?: string;
9+
paramsList?: string;
10+
paramType?: string;
11+
paramName?: string;
12+
file?: string;
13+
line?: string;
14+
}
15+
16+
interface Language {
17+
name: string;
18+
at: string;
19+
in: string;
20+
line: string;
21+
}
22+
23+
type Text = string;
24+
25+
interface Node {
26+
params: Array<{ name: string; type: string }>;
27+
type: string;
28+
lineNumber?: number;
29+
file?: string;
30+
method: string;
31+
spaces: string;
32+
}
33+
34+
type Element = Text | Node;
35+
36+
// Props
37+
const props = withDefaults(defineProps<{ stackTrace: string; options?: NetStackOptions }>(), {
38+
options: () => ({
39+
frame: "st-frame",
40+
type: "st-type",
41+
method: "st-method",
42+
paramsList: "st-frame-params",
43+
paramType: "st-param-type",
44+
paramName: "st-param-name",
45+
file: "st-file",
46+
line: "st-line",
47+
}),
48+
});
49+
50+
// Supported languages and their keywords
51+
const languages: Language[] = [
52+
{ name: "english", at: "at", in: "in", line: "line" },
53+
{ name: "danish", at: "ved", in: "i", line: "linje" },
54+
{ name: "german", at: "bei", in: "in", line: "Zeile" },
55+
{ name: "spanish", at: "en", in: "en", line: "línea" },
56+
{ name: "russian", at: "в", in: "в", line: "строка" },
57+
{ name: "chinese", at: "", in: "位置", line: "行号" },
58+
];
59+
60+
// Reactive variables and setup state
61+
const formattedStack = ref<Element[]>([]);
62+
const selectedLanguage = ref<Language>(languages[0]);
63+
64+
// Helper function to detect languages in the stack trace
65+
const detectLanguagesInOrder = (text: string): Language[] => {
66+
const languageRegexes = {
67+
english: /\s+at .*?\)/g,
68+
danish: /\s+ved .*?\)/g,
69+
german: /\s+bei .*?\)/g,
70+
spanish: /\s+en .*?\)/g,
71+
russian: /\s+в .*?\)/g,
72+
chinese: /\s+.*?\)/g,
73+
};
74+
75+
const detectedLanguages: Language[] = [];
76+
for (const lang in languageRegexes) {
77+
if (languageRegexes[lang as keyof typeof languageRegexes].test(text)) {
78+
const foundLang = languages.find((l) => l.name === lang);
79+
if (foundLang) {
80+
detectedLanguages.push(foundLang);
81+
}
82+
}
83+
}
84+
85+
return detectedLanguages;
86+
};
87+
88+
// Core formatting logic
89+
const formatStackTrace = (stackTrace: string, selectedLang: Language): Element[] => {
90+
const lines = stackTrace.split("\n");
91+
const fileAndLineNumberRegEx = new RegExp(`${selectedLang.in} (.+):${selectedLang.line} (\\d+)`);
92+
const atRegex = new RegExp(`(\\s*)(${selectedLang.at}) (.+?)\\((.*?)\\)`);
93+
94+
return lines.map((line) => {
95+
const match = line.match(atRegex);
96+
if (match) {
97+
const [, spaces, , methodWithType, paramsWithFile] = match;
98+
99+
const [type, method] = (() => {
100+
const parts = methodWithType.split(".");
101+
const method = parts.pop() ?? "";
102+
const type = parts.join(".");
103+
return [type, method];
104+
})();
105+
106+
const params = paramsWithFile.split(", ").map((param) => {
107+
const [paramType, paramName] = param.split(" ");
108+
return { name: paramName, type: paramType };
109+
});
110+
111+
const matchFile = line.match(fileAndLineNumberRegEx);
112+
let file, lineNumber;
113+
if (matchFile) {
114+
[, file, lineNumber] = matchFile;
115+
}
116+
117+
return <Node>{ method, type, params, file, lineNumber, spaces };
118+
} else {
119+
return line;
120+
}
121+
});
122+
};
123+
124+
// Process the provided stack trace
125+
const processStackTrace = (): void => {
126+
const rawContent = props.stackTrace;
127+
const detectedLanguages = detectLanguagesInOrder(rawContent);
128+
129+
if (!detectedLanguages.length) {
130+
formattedStack.value = [rawContent]; // If no language detected, output raw content
131+
return;
132+
}
133+
134+
selectedLanguage.value = detectedLanguages[0]; // Use the first detected language
135+
formattedStack.value = formatStackTrace(rawContent, selectedLanguage.value);
136+
};
137+
138+
watch(
139+
() => props.stackTrace,
140+
() => {
141+
processStackTrace();
142+
},
143+
{ immediate: true }
144+
);
145+
</script>
146+
147+
<template>
148+
<div class="stack-trace-container">
149+
<template v-for="line in formattedStack" :key="line">
150+
<template v-if="typeof line === 'string'">
151+
<span>{{ line }}</span>
152+
</template>
153+
<div v-else>
154+
{{ line.spaces }}{{ selectedLanguage.at }}
155+
<span :class="props.options.frame">
156+
<span :class="props.options.type">{{ line.type }}</span
157+
>.<span :class="props.options.method">{{ line.method }}</span
158+
>(<span :class="props.options.paramsList">
159+
<template v-for="(param, index) in line.params" :key="param.name">
160+
<span :class="props.options.paramType"> {{ param.type }}</span> <span :class="props.options.paramName">{{ param.name }}</span>
161+
<span v-if="index !== line.params.length - 1">, </span>
162+
</template> </span
163+
>)
164+
</span>
165+
<template v-if="line.file">
166+
{{ selectedLanguage.in }} <span :class="props.options.file">{{ line.file }}</span
167+
>:{{ selectedLanguage.line }} <span :class="props.options.line">{{ line.lineNumber }}</span>
168+
</template>
169+
</div>
170+
</template>
171+
</div>
172+
</template>
173+
174+
<style scoped>
175+
.stack-trace-container {
176+
font-family: monospace;
177+
white-space: pre-wrap;
178+
}
179+
180+
.st-frame {
181+
color: #00729c;
182+
}
183+
184+
.st-type {
185+
color: #a11;
186+
}
187+
188+
.st-method {
189+
color: #164;
190+
}
191+
192+
.st-file {
193+
color: #c67b3d;
194+
}
195+
196+
.st-line {
197+
color: #6c757d;
198+
}
199+
200+
.st-param-type {
201+
font-style: italic;
202+
color: #6b82ce;
203+
}
204+
205+
.st-param-name {
206+
font-weight: bold;
207+
color: #343a40;
208+
}
209+
210+
.st-frame-params {
211+
color: #495057;
212+
}
213+
</style>
Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,43 @@
11
<script setup lang="ts">
2-
import CodeEditor from "@/components/CodeEditor.vue";
32
import { useMessageStore } from "@/stores/MessageStore";
43
import LoadingSpinner from "@/components/LoadingSpinner.vue";
54
import { storeToRefs } from "pinia";
5+
import StacktraceFormatter from "@/components/messages2/StacktraceFormatter.vue";
6+
import CopyToClipboard from "@/components/CopyToClipboard.vue";
67
78
const { state } = storeToRefs(useMessageStore());
89
</script>
910

1011
<template>
1112
<div v-if="state.failed_to_load" class="alert alert-info">Stacktrace not available.</div>
1213
<LoadingSpinner v-else-if="state.loading" />
13-
<CodeEditor v-else :model-value="state.data.failure_metadata.exception?.stack_trace!" language="powershell" :read-only="true" :show-gutter="false"></CodeEditor>
14+
<div v-else class="wrapper">
15+
<div class="toolbar">
16+
<CopyToClipboard class="clipboard" :value="state.data.failure_metadata.exception?.stack_trace!" />
17+
</div>
18+
<StacktraceFormatter :stack-trace="state.data.failure_metadata.exception?.stack_trace!" />
19+
</div>
1420
</template>
21+
22+
<style scoped>
23+
.toolbar {
24+
background-color: #f3f3f3;
25+
border: #8c8c8c 1px solid;
26+
border-radius: 3px;
27+
padding: 5px;
28+
margin-bottom: 0.5rem;
29+
display: flex;
30+
flex-direction: row;
31+
justify-content: flex-end;
32+
align-items: center;
33+
}
34+
35+
.wrapper {
36+
margin-top: 5px;
37+
margin-bottom: 15px;
38+
border-radius: 0.5rem;
39+
padding: 0.5rem;
40+
border: 1px solid #ccc;
41+
background: white;
42+
}
43+
</style>

0 commit comments

Comments
 (0)