Skip to content

Error Tracking

The SDK can automatically catch uncaught exceptions and unhandled promise rejections, logging them as fatal.

Browser (@gunsole/web — attached automatically):

  • window.onerror → logs to bucket global_error
  • window.onunhandledrejection → logs to bucket unhandled_rejection

Node.js (@gunsole/core — attach manually):

  • process.uncaughtException → logs to bucket uncaught_exception
  • process.unhandledRejection → logs to bucket unhandled_rejection
// @gunsole/core — manual attachment
gunsole.attachGlobalErrorHandlers();
// Detach when done (e.g., in tests)
gunsole.detachGlobalErrorHandlers();
// @gunsole/web — automatically attached on createGunsoleClient()
componentDidCatch(error: Error, info: React.ErrorInfo) {
gunsole.fatal({
bucket: "error_boundary",
message: error.message,
context: { stack: error.stack, componentStack: info.componentStack },
});
}
useEffect(() => {
gunsole.fatal({
bucket: "fatal",
message: error.message,
context: { name: error.name, stack: error.stack, digest: (error as any).digest },
});
}, [error]);
class GunsoleErrorHandler implements ErrorHandler {
handleError(error: unknown): void {
const err = error instanceof Error ? error : new Error(String(error));
gunsole.fatal({ bucket: "fatal", message: err.message, context: { name: err.name, stack: err.stack } });
}
}
<ErrorBoundary fallback={(err) => {
gunsole.fatal({ bucket: "fatal", message: err instanceof Error ? err.message : String(err) });
return <div>Error</div>;
}}>

For errors you catch yourself:

try {
await riskyOperation();
} catch (err) {
gunsole.error({
bucket: "api",
message: `Operation failed: ${err.message}`,
context: {
error: err.message,
stack: err.stack,
code: err.code,
},
tags: {
operation: "riskyOperation",
errorType: err.constructor.name,
},
});
}
const originalFetch = window.fetch;
window.fetch = async (...args) => {
try {
const response = await originalFetch(...args);
if (!response.ok) {
gunsole.error({
bucket: "http",
message: `HTTP ${response.status}: ${args[0]}`,
context: { status: response.status, url: String(args[0]) },
tags: { status: String(response.status) },
});
}
return response;
} catch (err) {
gunsole.error({
bucket: "http",
message: `Network error: ${args[0]}`,
context: { error: err instanceof Error ? err.message : String(err), url: String(args[0]) },
});
throw err;
}
};
axios.interceptors.response.use(
(response) => response,
(error) => {
gunsole.error({
bucket: "http",
message: `HTTP ${error.response?.status || "network"}: ${error.config.url}`,
context: {
url: error.config.url,
method: error.config.method,
status: error.response?.status,
data: error.response?.data,
},
tags: {
status: String(error.response?.status || "network_error"),
method: error.config.method.toUpperCase(),
},
});
return Promise.reject(error);
}
);

Use consistent tags for errors so you can filter by error type in the desktop app:

gunsole.error({
bucket: "api",
message: "Payment failed",
tags: {
error_type: "validation", // validation, auth, network, timeout, unknown
severity: "high", // low, medium, high, critical
recoverable: "false", // true, false
},
context: { code: "card_declined", provider: "stripe" },
});