Skip to content

Commit 5bd7c7f

Browse files
committed
feat: add Laravel stack trace visualization tab
1 parent 503c545 commit 5bd7c7f

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

public/app.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/mix-manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"/app.js": "/app.js?id=74d3e481ad3e8fa14f1daaa0aa46e109",
3-
"/app.css": "/app.css?id=5593a0331dd40729ff41e32a6035d872",
2+
"/app.js": "/app.js?id=8cfcfcd4a67b98d6d1cca994164fbb8e",
3+
"/app.css": "/app.css?id=ffb86c9991a1ac32492c4abb96cd75f4",
44
"/img/log-viewer-128.png": "/img/log-viewer-128.png?id=d576c6d2e16074d3f064e60fe4f35166",
55
"/img/log-viewer-32.png": "/img/log-viewer-32.png?id=f8ec67d10f996aa8baf00df3b61eea6d",
66
"/img/log-viewer-64.png": "/img/log-viewer-64.png?id=8902d596fc883ca9eb8105bb683568c6"

resources/js/components/BaseLogTable.vue

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,45 @@
8787
<mail-text-preview :mail="log.extra.mail_preview" />
8888
</tab-content>
8989

90+
<tab-content v-if="hasLaravelStackTrace(log)" tab-value="stack_trace">
91+
<div class="p-4 lg:p-8">
92+
<!-- Exception Header -->
93+
<div v-if="getStackTraceData(log).header" class="mb-6 pb-4 border-b border-gray-200 dark:border-gray-600">
94+
<div class="text-red-600 dark:text-red-400 font-semibold text-lg mb-2">
95+
{{ getStackTraceData(log).header.type }}
96+
</div>
97+
<div class="text-gray-800 dark:text-gray-200 text-base mb-2">
98+
{{ getStackTraceData(log).header.message }}
99+
</div>
100+
<div class="text-sm text-gray-600 dark:text-gray-400 font-mono">
101+
in {{ getStackTraceData(log).header.file }}:{{ getStackTraceData(log).header.line }}
102+
</div>
103+
</div>
104+
105+
<!-- Stack Trace Frames -->
106+
<div class="space-y-2">
107+
<div v-for="(frame, frameIndex) in getStackTraceData(log).frames" :key="frameIndex"
108+
class="mb-2 border-b border-gray-100 dark:border-gray-700 pb-2 last:border-b-0">
109+
<div class="flex items-start gap-3">
110+
<div class="text-xs text-gray-500 dark:text-gray-400 font-mono w-8 flex-shrink-0 pt-1">
111+
#{{ frame.number }}
112+
</div>
113+
<div class="flex-1 min-w-0">
114+
<div v-if="frame.file" class="text-sm mb-1">
115+
<span class="font-mono text-blue-600 dark:text-blue-400 break-all">{{ frame.file }}</span>
116+
<span class="text-gray-500 dark:text-gray-400">:</span>
117+
<span class="font-mono text-orange-600 dark:text-orange-400">{{ frame.line }}</span>
118+
</div>
119+
<div class="text-sm text-gray-800 dark:text-gray-200 font-mono break-all">
120+
{{ frame.call }}
121+
</div>
122+
</div>
123+
</div>
124+
</div>
125+
</div>
126+
</div>
127+
</tab-content>
128+
90129
<tab-content tab-value="raw">
91130
<pre class="log-stack" v-html="highlightSearchResult(log.full_text, searchStore.query)"></pre>
92131
<template v-if="hasContext(log)">
@@ -195,10 +234,16 @@ const getExtraTabsForLog = (log) => {
195234
}
196235
197236
const getTabsForLog = (log) => {
198-
return [
199-
...getExtraTabsForLog(log),
200-
{ name: 'Raw', value: 'raw' },
201-
].filter(Boolean);
237+
const tabs = [...getExtraTabsForLog(log)];
238+
239+
tabs.push({ name: 'Raw', value: 'raw' });
240+
241+
// Add Stack Trace tab for Laravel logs with stack traces
242+
if (hasLaravelStackTrace(log)) {
243+
tabs.push({ name: 'Stack Trace', value: 'stack_trace' });
244+
}
245+
246+
return tabs.filter(Boolean);
202247
}
203248
204249
const prepareContextForOutput = (context) => {
@@ -211,6 +256,57 @@ const prepareContextForOutput = (context) => {
211256
}, 2);
212257
}
213258
259+
const hasLaravelStackTrace = (log) => {
260+
const exception = Array.isArray(log.context)
261+
? log.context.find(item => item.exception)?.exception
262+
: log.context.exception;
263+
return exception && typeof exception === 'string' && exception.includes('[stacktrace]');
264+
}
265+
266+
const getStackTraceData = (log) => {
267+
const exception = Array.isArray(log.context)
268+
? log.context.find(item => item.exception)?.exception
269+
: log.context.exception;
270+
271+
if (!exception || typeof exception !== 'string') {
272+
return { header: null, frames: [] };
273+
}
274+
275+
// Parse exception header
276+
const headerMatch = exception.match(/^\[object\]\s*\(([^(]+)\(code:\s*\d+\):\s*(.+?)\s+at\s+(.+?):(\d+)\)/);
277+
const header = headerMatch ? {
278+
type: headerMatch[1].trim(),
279+
message: headerMatch[2].trim(),
280+
file: headerMatch[3].trim(),
281+
line: parseInt(headerMatch[4])
282+
} : null;
283+
284+
// Parse stack trace frames
285+
const stacktraceMatch = exception.match(/\[stacktrace\]([\s\S]*?)(?:\n\n|\n$|$)/);
286+
const frames = [];
287+
if (stacktraceMatch) {
288+
const frameRegex = /#(\d+)\s+(.+?)(?:\n|$)/g;
289+
let match;
290+
while ((match = frameRegex.exec(stacktraceMatch[1])) !== null) {
291+
const frameLine = match[2].trim();
292+
const fileMatch = frameLine.match(/^(.+?)\((\d+)\):\s*(.+)$/);
293+
frames.push(fileMatch ? {
294+
number: parseInt(match[1]),
295+
file: fileMatch[1],
296+
line: parseInt(fileMatch[2]),
297+
call: fileMatch[3]
298+
} : {
299+
number: parseInt(match[1]),
300+
file: '',
301+
line: 0,
302+
call: frameLine
303+
});
304+
}
305+
}
306+
307+
return { header, frames };
308+
}
309+
214310
const tableColumns = computed(() => {
215311
// the extra two columns are for the expand/collapse and log index columns
216312
return logViewerStore.columns.length + 2;

0 commit comments

Comments
 (0)