How to add Sentry to Chrome Extension
Table Of Contents
- Table Of Contents
- Into
- 1. Install Sentry
- 2. Disable Filters in Project Settings
- 3. Capture Errors in React
- 4. Manual Error Reporting
- Benefits of This Approach
Into
Sentry is a powerful tool for monitoring and debugging errors in your applications. Integrating it with a Chrome extension can help you capture and diagnose issues effectively. This guide walks you through setting up Sentry for a Chrome extension with a React-based frontend.
1. Install Sentry
To start, install the Sentry browser package:
npm install --save @sentry/browser
2. Disable Filters in Project Settings
If Sentry doesn’t capture errors from your Chrome extension, it might be due to inbound filters. To ensure all errors are recorded:
- Navigate to Projects -> MY_PROJECT -> Project Settings -> Inbound Filters in your Sentry dashboard.
- Disable any active filters that might block errors from Chrome extensions.
3. Capture Errors in React
To capture React errors without initializing Sentry.init() globally, you can manually configure a Sentry client and use it with an ErrorBoundary component. This approach prevents global state conflicts in shared environments, such as browser extensions.
3.1 Configure the Sentry Client
Create a custom Sentry client:
import {
BrowserClient,
defaultStackParser,
getDefaultIntegrations,
makeFetchTransport,
Scope,
type EventHint,
} from "@sentry/browser";
import type { ErrorInfo } from "react";
// Configure client without global state
const integrations = getDefaultIntegrations({}).filter((integration) => {
return !["BrowserApiErrors", "Breadcrumbs", "GlobalHandlers"].includes(
integration.name,
);
});
const client = new BrowserClient({
dsn: "YOUR_DSN_URL",
transport: makeFetchTransport,
stackParser: defaultStackParser,
integrations: integrations,
enabled: import.meta.env.PROD, // Enable only in production
});
const scope = new Scope();
scope.setClient(client);
// Function to capture exceptions
export const captureException = (error: Error, errorInfo: ErrorInfo) => {
if (import.meta.env.DEV) {
console.group("Error captured in development:");
console.error("Error:", error);
console.info("Component Stack:", errorInfo.componentStack);
console.groupEnd();
return;
}
scope.addEventProcessor((event) => {
return { ...event, extra: { ...event.extra, errorInfo } };
});
const hint: EventHint = {
data: { react: errorInfo },
};
client.captureException(error, hint);
};
3.2 Create an ErrorBoundary Component
Use an ErrorBoundary to handle errors gracefully and send them to Sentry:
import React from "react";
import { captureException } from "@/utils/sentryCapturer";
interface Props {
children: React.ReactNode;
fallback?: React.ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
captureException(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
export const ErrorFallback = ({ error }: { error?: Error }) => (
<div className="error-container">
<h2>Something went wrong</h2>
<p>An error occurred while loading the component. We're working on fixing it.</p>
{error && <pre>{error.message}</pre>}
<button onClick={() => window.location.reload()}>Refresh Page</button>
</div>
);
3.3 Use ErrorBoundary in Your Application
Wrap components prone to errors with the ErrorBoundary:
import React from "react";
import { captureException } from "@/utils/sentryCapturer.ts";
interface Props {
children: React.ReactNode;
fallback?: React.ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
captureException(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
export const ErrorFallback = ({ error }: { error?: Error }) => (
<div className="min-h-[200px] p-6 rounded-lg bg-gradient-to-br from-red-50 to-red-100 border border-red-200 shadow-sm">
<div className="flex flex-col items-center text-center space-y-4">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h2 className="text-xl font-semibold text-red-700">
Something went wrong
</h2>
<p className="text-xs text-red-600 max-w-md">
An error occurred while loading the component.
</p>
<p className="text-xs text-red-600 max-w-md">
We're already working on fixing it.
</p>
{error && (
<div className="mt-4 p-3 bg-white/50 rounded-md backdrop-blur-sm w-full">
<p className="text-xs font-mono text-red-800 break-all">
{error.message}
</p>
</div>
)}
<button
onClick={() => window.location.reload()}
className="mt-4 px-4 py-2 bg-red-500 hover:bg-red-600 active:bg-red-700 text-white rounded-lg
transition-all duration-200 transform hover:scale-105 active:scale-100
shadow-md hover:shadow-lg active:shadow-sm"
>
Refresh Page
</button>
</div>
</div>
);
// Test component that triggers an error
///
// Usage:
//
// <ErrorBoundary fallback={<ErrorFallback />}>
// <BuggyCounter />
// </ErrorBoundary>
//
export class BuggyCounter extends React.Component<Record<string, unknown>, { counter: number }> {
state = { counter: 0 };
handleClick = () => {
this.setState(({counter}) => ({
counter: counter + 1
}));
};
render() {
if (this.state.counter === 5) {
throw new Error('Error simulation!');
}
return (
<button
onClick={this.handleClick}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600
transition-all duration-200 transform hover:scale-105 active:scale-100
shadow-md hover:shadow-lg mb-4"
>
Clicks: {this.state.counter}
</button>
);
}
}
Test ErrorBoundary out:
import React from "react";
import ReactDOM from "react-dom";
import { ErrorBoundary, ErrorFallback, BuggyCounter } from "./ErrorBoundary";
ReactDOM.render(
<React.StrictMode>
<ErrorBoundary fallback={<ErrorFallback />}>
<BuggyCounter />
</ErrorBoundary>
</React.StrictMode>,
document.getElementById("root")
);
4. Manual Error Reporting
To capture errors programmatically, use the captureException function:
import { captureException } from "./sentryClient";
function ExampleComponent() {
const handleClick = () => {
try {
throw new Error("Example error!");
} catch (error) {
captureException(error);
}
};
return <button onClick={handleClick}>Simulate Error</button>;
}
Benefits of This Approach
- No Global State Pollution: Ensures Sentry doesn’t interfere with other parts of your app.
- Fine-Grained Control: Precisely manage what errors and contexts are sent to Sentry.
- Browser Extension Compatibility: Works seamlessly in shared environments like Chrome extensions.
By following this guide, you’ll have a robust error monitoring solution for your Chrome extension that integrates seamlessly with React and Sentry.