Logger: serialize errors

This commit is contained in:
Enrico Ros
2025-05-01 01:33:52 -07:00
parent 910cbb542e
commit 0e0ed3d657
4 changed files with 45 additions and 6 deletions
+3 -1
View File
@@ -1,3 +1,5 @@
import { serializeError } from '~/common/util/errorUtils';
import type { ClientLogger, LogEntry, LogLevel, LogOptions, LogSource } from './logger.types';
import { LoggerActions, useLoggerStore } from './store-logger';
@@ -85,7 +87,7 @@ class LoggerImplementation implements ClientLogger {
// combine options
const finalOptions = options || {};
const finalSource = source || finalOptions.source || 'unknown';
const finalDetails = details || finalOptions.details;
const finalDetails = serializeError(details || finalOptions.details); // serializeError because otherwise 'Error' wouldn't be serializable, and would appear as {}
// prepare actions - handle both options.action and options.actions
let actions = finalOptions.actions || [];
+1 -1
View File
@@ -41,7 +41,7 @@ export function LogEntryDetails(props: {
return (
<Card variant='outlined' sx={{ backgroundColor: 'background.popup' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography level='title-md'>{entry.message}</Typography>
<Button
size='sm'
@@ -99,7 +99,7 @@ export function LogViewerDialog(props: {
title='Client Logs'
unfilterBackdrop
// themedColor='neutral'
sx={{ maxWidth: undefined }}
sx={{ maxWidth: undefined, overflow: 'hidden' }}
>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' }, gap: 2, mb: 1 }}>
@@ -159,7 +159,7 @@ export function LogViewerDialog(props: {
<Typography level='body-lg'>No logs to display</Typography>
</Box>
) : (
<Box sx={{ minHeight: '12rem', maxHeight: 'calc(100vh - 18rem)', overflow: 'auto', my: 1 }}>
<Box sx={{ minHeight: '12rem', overflow: 'auto', my: 1 }}>
<Table
size='sm'
variant='outlined'
@@ -177,7 +177,7 @@ export function LogViewerDialog(props: {
<th style={{ width: '30px' }}></th>
<th style={{ width: '120px' }}>Time</th>
<th style={{ width: '80px' }}>Level</th>
<th style={{ width: '100px' }}>Source</th>
<th style={{ width: '120px' }}>Source</th>
<th style={{ minWidth: '100px' }}>Message</th>
<th style={{ width: '100px' }}>Actions</th>
</tr>
@@ -201,7 +201,7 @@ export function LogViewerDialog(props: {
{entry.level}
</Chip>
</td>
<td>{entry.source}</td>
<td style={{ whiteSpace: 'nowrap' }}>{entry.source}</td>
<td className='agi-ellipsize'>{entry.message}</td>
<td>
{entry.actions && entry.actions.length > 0 && (
+37
View File
@@ -55,3 +55,40 @@ function safeObjectToString(obj: object): string {
}
return `{ ${pairs.join(', ')} }`;
}
/**
* Serialize an error object to a plain object for storage or transmission.
*/
export function serializeError(value: any): any {
// handle Error objects
if (value instanceof Error) {
return {
_isError: true, // Mark as serialized error for identification
name: value.name ?? 'SError',
message: value.message ?? 'No SMessage',
...(value.stack !== undefined && { stack: value.stack }), // Include stack if available
...(value.cause !== undefined && { cause: serializeError(value.cause) }), // Recursively serialize cause
// Capture other properties
...Object.fromEntries(
Object.entries(value).filter(([k]) => !['name', 'message', 'stack', 'cause'].includes(k)),
),
};
}
// handle objects that might contain errors
if (value && typeof value === 'object') {
if (Array.isArray(value)) {
return value.map(serializeError);
}
const result: Record<string, any> = {};
for (const [key, val] of Object.entries(value)) {
result[key] = serializeError(val);
}
return result;
}
// Return primitives as-is
return value;
}