diff --git a/src/components/LogsTable.tsx b/src/components/LogsTable.tsx index 5d8f27b..14175d2 100644 --- a/src/components/LogsTable.tsx +++ b/src/components/LogsTable.tsx @@ -10,7 +10,7 @@ interface LogEntry { level: string fqns: string msg: string - exception: string | null + exception: string[] | null process_uuid: string } @@ -19,6 +19,22 @@ interface LogsTableProps { title: string } +interface ExceptionLine { + type: 'exception' + raw: string + formatted: string +} + +interface FrameLine { + type: 'frame' + namespace: string + method: string + file: string + lineNum: number + raw: string + isMetabaseFrame: boolean +} + const getLevelColor = (level: string) => { switch (level.toUpperCase()) { case 'DEBUG': @@ -106,6 +122,110 @@ const LogsTable: React.FC = ({ logs }) => { [filteredLogs] ) + const prettyPrintClojure = (str: string) => { + let depth = 0 + let inString = false + let formatted = '' + + for (let i = 0; i < str.length; i++) { + const char = str[i] + + // Handle string literals + if (char === '"' && str[i - 1] !== '\\') { + inString = !inString + formatted += char + continue + } + + if (inString) { + formatted += char + continue + } + + // Handle brackets and braces + if (char === '{' || char === '[' || char === '(') { + depth++ + formatted += char + '\n' + ' '.repeat(depth) + } else if (char === '}' || char === ']' || char === ')') { + depth-- + formatted += '\n' + ' '.repeat(depth) + char + } else if ( + char === ' ' && + (str[i - 1] === ',' || str[i - 1] === '}' || str[i - 1] === ']' || str[i - 1] === ')') + ) { + formatted += '\n' + ' '.repeat(depth) + } else { + formatted += char + } + } + return formatted + } + + const preprocessStacktrace = (trace: string) => { + if (!trace) return '' + return trace.replace(/\t+at\s+|\s+at\s+/g, '\n at ').trim() + } + + const formatStacktrace = (lines: string[]) => { + if (!lines) return [] + + const formattedLines = new Array() + + // Process first line (exception) + if (lines[0]) { + const excLine = preprocessStacktrace(lines[0]) + const match = excLine.match(/^([^{[\n]+)({.+|[.+])/s) + if (match) { + // eslint-disable-next-line no-unused-vars + const [_, prefix, clojureData] = match + formattedLines.push({ + type: 'exception', + raw: excLine, + formatted: prefix + '\n' + prettyPrintClojure(clojureData), + }) + } else { + formattedLines.push({ + type: 'exception', + raw: excLine, + formatted: excLine, + }) + } + } + + // Process stack frames + const framePattern = /\s*at\s+([a-zA-Z0-9.$_/]+(?:\$[^(]+)?)\s*\(([\w.]+):(\d+)\)/ + + lines.slice(1).forEach((line) => { + const frameLine = preprocessStacktrace(line) + const match = frameLine.match(framePattern) + if (match) { + // eslint-disable-next-line no-unused-vars + const [_, fullMethod, file, lineNum] = match + const lastDotIndex = fullMethod.lastIndexOf('.') + const namespace = fullMethod.substring(0, lastDotIndex) + const method = fullMethod.substring(lastDotIndex + 1) + + const isMetabaseFrame = namespace.startsWith('metabase') + + if (isMetabaseFrame || !showOnlyMetabaseFrames) { + formattedLines.push({ + type: 'frame', + namespace, + method, + file, + lineNum: parseInt(lineNum), + raw: frameLine, + isMetabaseFrame, + }) + } + } + }) + + return formattedLines + } + + const [showOnlyMetabaseFrames, setShowOnlyMetabaseFrames] = useState(true) + return ( <> = ({ logs }) => { onChange={(e) => setSearchQuery(e.target.value)} className="mb-4" /> +
+ +
@@ -159,7 +290,51 @@ const LogsTable: React.FC = ({ logs }) => {
Exception:
-                              {log.exception}
+                              {formatStacktrace(log.exception).map((line, i) => {
+                                if (line.type === 'exception') {
+                                  return (
+                                    
+
+ {line.formatted} +
+
+ ) + } + return ( +
+ + at{' '} + + + {line.namespace} + + + .{line.method} + + ( + {line.file} + : + {line.lineNum} + ) +
+ ) + })}
)} diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 1021091..43b6fd8 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -74,10 +74,7 @@ const TableCell = React.forwardRef< >(({ className, ...props }, ref) => (
))