Reporting Errors to a Service (Part 2) #144
Chapter 05: Performance and Optimization
Series 18: Error Boundaries
📖 Introduction
In Reporting Errors to a Service (Part 1), we discussed the critical importance of client-side error reporting and got an overview of services like Sentry. This second part gets practical: we'll walk through integrating an error reporting service (using Sentry as our primary example) with both our custom class-based ErrorBoundary
and the react-error-boundary
library. We'll cover initializing the SDK, capturing errors, and enriching reports with context.
📚 Prerequisites
Before we begin, ensure you have:
- Understood the concepts from Part 1 (why error reporting is needed, overview of services, source maps).
- A reusable class-based
ErrorBoundary
(from Articles 138-139) OR familiarity withreact-error-boundary
(from Articles 141-142). - An account with an error reporting service like Sentry (a free tier is usually available for experimentation). You'll need a DSN (Data Source Name) key from your Sentry project.
🎯 Article Outline: What You'll Master
In this article, you will learn:
- ✅ Setting up Sentry SDK: Initializing Sentry in a React application.
- ✅ Integrating Sentry with a Custom Class
ErrorBoundary
: ModifyingcomponentDidCatch
to report errors. - ✅ Integrating Sentry with
react-error-boundary
: Using theonError
prop. - ✅ Enriching Error Reports: Adding user context, tags, and custom data.
- ✅ Testing the Integration: Intentionally throwing an error to see it appear in the Sentry dashboard.
- ✅ Source Map Uploads (Conceptual Overview): The importance and general process.
🧠 Section 1: Setting up the Sentry SDK in Your React App
First, you need to install the Sentry SDK for React.
npm install @sentry/react @sentry/tracing
# or
yarn add @sentry/react @sentry/tracing
@sentry/react
: The core React SDK.@sentry/tracing
: For performance monitoring and capturing more detailed transaction data (optional but often useful).
Next, initialize Sentry as early as possible in your application's lifecycle, typically in your main index.js
or App.js
file (before your main app render).
// src/index.js (or your app's entry point)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App'; // Your main application component
import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";
// --- Sentry Initialization ---
Sentry.init({
dsn: "YOUR_SENTRY_DSN_HERE", // Replace with your actual DSN from Sentry project settings
integrations: [
new BrowserTracing({
// Optionally trace navigations if using React Router or similar
// routingInstrumentation: Sentry.reactRouterV6Instrumentation(React.useEffect, React.useLocation, React.useNavigationType, React.createRoutesFromChildren, React.matchRoutes),
}),
// Add other integrations if needed
],
// Performance Monitoring
tracesSampleRate: 1.0, // Capture 100% of transactions during development/testing. Adjust to a lower rate in production (e.g., 0.1 for 10%).
// Session Replay (Optional, if you want to use Sentry's Replay feature)
// replaysSessionSampleRate: 0.1, // This percentage of sessions will be recorded
// replaysOnErrorSampleRate: 1.0, // If an error happens during a session, replay it.
// Environment and Release (Highly Recommended for production)
environment: process.env.NODE_ENV, // e.g., 'development', 'staging', 'production'
release: process.env.REACT_APP_SENTRY_RELEASE, // e.g., '[email protected]' - set this during your build process
// Enable debug mode in development to see what Sentry is doing
debug: process.env.NODE_ENV === 'development',
});
// --- End of Sentry Initialization ---
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Key Sentry.init
Options:
dsn
(Required): Your Data Source Name from your Sentry project settings. This tells the SDK where to send errors. Never commit your actual DSN directly if your code is public; use environment variables.integrations
: An array of Sentry integrations.BrowserTracing
enables performance monitoring. There are integrations for various frameworks and libraries (e.g., React Router, Redux).tracesSampleRate
: For performance monitoring. A value between 0 (0%) and 1 (100%) indicating the percentage of transactions to send to Sentry. Start with 1.0 in dev, reduce for production to manage volume.replaysSessionSampleRate
/replaysOnErrorSampleRate
(Optional): For Sentry Session Replay.environment
: Helps you filter issues by environment (e.g.,development
,production
). Set via environment variables.release
: Associates errors with a specific version of your application. This is crucial for tracking when bugs were introduced or fixed. You'd typically set this during your CI/CD build process (e.g.,REACT_APP_SENTRY_RELEASE="my-app@${process.env.GIT_COMMIT_SHA}"
).debug
: Whentrue
, Sentry logs verbose information to the console about what it's doing. Useful for development.
With this initialization, Sentry will automatically capture:
- Unhandled JavaScript exceptions (
window.onerror
). - Unhandled promise rejections (
window.onunhandledrejection
).
💻 Section 2: Integrating Sentry with a Custom Class ErrorBoundary
Let's modify the componentDidCatch
method of our custom ErrorBoundary
(from Article 139) to report errors to Sentry.
// src/components/ErrorBoundary.jsx
import React, { Component } from 'react';
import * as Sentry from '@sentry/react'; // Import Sentry
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
eventId: null // To store the Sentry event ID
};
}
static getDerivedStateFromError(error) {
return { hasError: true, error: error };
}
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo: errorInfo });
// --- Sentry Reporting ---
Sentry.withScope(scope => {
scope.setTag("ErrorBoundaryComponent", "CustomErrorBoundary"); // Add a custom tag
scope.setExtras(errorInfo); // Send componentStack and other info as 'extra' data
const eventId = Sentry.captureException(error); // Capture the exception
this.setState({ eventId }); // Store the event ID if you want to display it
console.log(`Error reported to Sentry with event ID: ${eventId}`);
});
// --- End of Sentry Reporting ---
// Keep local console logging for development convenience
console.error("CustomErrorBoundary caught:", error, errorInfo.componentStack);
}
resetErrorBoundary = () => { /* ... same as before ... */ };
render() {
if (this.state.hasError) {
// You could optionally display the Sentry eventId to the user for support
// if (this.state.eventId) { return <p>Error ID: {this.state.eventId}. Please provide this to support.</p>; }
// ... (rest of your fallback rendering logic from Article 139) ...
// For example, using fallbackComponent:
if (this.props.fallbackComponent) {
const FallbackComponent = this.props.fallbackComponent;
return <FallbackComponent error={this.state.error} errorInfo={this.state.errorInfo} resetError={this.resetErrorBoundary} eventId={this.state.eventId} />;
}
// Default fallback
return (
<div>
<h2>Oops! Something went wrong (Custom EB).</h2>
<button onClick={this.resetErrorBoundary}>Try Again</button>
{this.state.eventId && <p><small>Error Ref: {this.state.eventId}</small></p>}
{/* ... dev details ... */}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Changes in componentDidCatch
:
import * as Sentry from '@sentry/react';
: Import the Sentry SDK.Sentry.withScope(scope => { ... });
: This is good practice. It allows you to add context (tags, extras, user info) that is specific to this error event without affecting global Sentry scope.scope.setTag("ErrorBoundaryComponent", "CustomErrorBoundary");
: Adds a custom tag to the Sentry event. Tags are searchable key-value pairs.scope.setExtras(errorInfo);
: Sends theerrorInfo
object (which containscomponentStack
) as "extra data" with the Sentry event. This is very useful for debugging.const eventId = Sentry.captureException(error);
: This is the core call that sends the error to Sentry. It returns an event ID.this.setState({ eventId });
: We store theeventId
. You could potentially display this ID to the user in the fallback UI, which they can then provide to your support team for quick lookup in Sentry.
Now, any error caught by this ErrorBoundary
will be sent to your Sentry dashboard.
🛠️ Section 3: Integrating Sentry with react-error-boundary
The react-error-boundary
library makes Sentry integration even simpler using its onError
prop.
// src/AppWithReactErrorBoundaryAndSentry.jsx
import React, { useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import * as Sentry from '@sentry/react'; // Import Sentry
import FunctionalWidget from './components/FunctionalWidget'; // Your component that might error
// Fallback UI (can be the same as before)
function SentryErrorFallback({ error, resetErrorBoundary, componentStack, eventId }) {
return (
<div role="alert" style={{ padding: '20px', border: '1px solid red', margin: '10px', backgroundColor: '#ffe0e0' }}>
<h3>App Error (react-error-boundary & Sentry):</h3>
<pre style={{ color: 'red' }}>{error.message}</pre>
{/* Sentry.showReportDialog can be used here if desired */}
<button onClick={() => {
Sentry.captureMessage('User clicked Try Again after an error.', 'info');
resetErrorBoundary();
}}
style={{ padding: '8px 15px' }}
>
Try again
</button>
{eventId && <p><small>Error Reference: {eventId}</small></p>}
{process.env.NODE_ENV === 'development' && componentStack && (
<details><summary>Component Stack</summary><pre>{componentStack}</pre></details>
)}
</div>
);
}
// The onError handler for react-error-boundary
const handleBoundaryError = (error, info) => {
console.error("react-error-boundary caught an error via onError:", error);
console.error("Component stack (from react-error-boundary):", info.componentStack);
// Send to Sentry
Sentry.withScope(scope => {
scope.setTag("ErrorBoundaryLibrary", "react-error-boundary");
scope.setExtra("componentStack", info.componentStack); // Pass componentStack as extra
// You could add more context here if needed
// scope.setUser({ email: "[email protected]" });
Sentry.captureException(error);
});
};
function AppWithReactErrorBoundaryAndSentry() {
const [widgetKey, setWidgetKey] = useState(0);
const handleReset = () => {
setWidgetKey(prevKey => prevKey + 1);
};
return (
<div style={{ padding: '20px' }}>
<h1>Using `react-error-boundary` with Sentry</h1>
<ErrorBoundary
FallbackComponent={SentryErrorFallback} // SentryErrorFallback can also capture eventId if passed by ErrorBoundary (requires modification or custom HOC)
// For simplicity, react-error-boundary doesn't pass eventId to FallbackComponent by default.
// We'd typically use Sentry.lastEventId() if needed, or Sentry's user feedback widget.
onReset={handleReset}
resetKeys={[widgetKey]}
onError={handleBoundaryError} // Our Sentry logging function
>
<FunctionalWidget title={`Sentry Widget (key: ${widgetKey})`} />
</ErrorBoundary>
</div>
);
}
export default AppWithReactErrorBoundaryAndSentry;
Key Points for react-error-boundary
Integration:
onError
Prop: This prop on<ErrorBoundary>
takes a function that receives(error, info)
.info
is an object{ componentStack: '...' }
.handleBoundaryError
Function: This function is where you callSentry.captureException(error)
and add any desired scope enhancements (tags, extras).FallbackComponent
: TheFallbackComponent
(SentryErrorFallback
in this example) receiveserror
andresetErrorBoundary
. It doesn't directly receive the SentryeventId
fromreact-error-boundary
's default props. If you need theeventId
in the fallback:- You could call
Sentry.lastEventId()
within the fallback (if the event was just sent). - Sentry offers a "User Feedback" widget (
Sentry.showReportDialog({ eventId })
) which can be triggered from the fallback, automatically linking feedback to the error event. This is often the preferred way.
- You could call
Sentry's SDK is designed to make this integration straightforward.
🔬 Section 4: Enriching Error Reports - User, Tags, Extras
To make error reports more actionable, add context:
-
User Context: Knowing which user experienced an error is invaluable.
// Call this when a user logs in or their info is known
Sentry.setUser({
id: 'user123',
email: '[email protected]',
username: 'janedoe',
// You can add other custom attributes
// company_id: 'companyXYZ'
});
// To clear user context on logout: Sentry.setUser(null);Sentry will automatically associate subsequent errors with this user.
-
Tags (
scope.setTag(key, value)
): Tags are indexed and searchable in Sentry, good for filtering issues (e.g.,scope.setTag('feature', 'checkout');
,scope.setTag('user_role', 'admin');
). -
Extra Data (
scope.setExtra(key, value)
orscope.setExtras(object)
): For arbitrary, non-indexed data that provides more detail (e.g., relevant application state, feature flags active,componentStack
). This data appears in the "Additional Data" section of an issue in Sentry.
You can set these globally (e.g., after Sentry init for setUser
) or within Sentry.withScope()
for specific error events in componentDidCatch
or your onError
handler.
✨ Section 5: Testing the Integration and Source Maps
Testing:
- Ensure Sentry is Initialized: Your
dsn
inSentry.init
must be correct. - Trigger an Error: In your test app, click the button in
FunctionalWidget
to cause an error. - Check Sentry Dashboard:
- Go to your Sentry project at sentry.io.
- You should see a new "Issue" appear within a few moments.
- Click on the issue to see details: error message, stack trace, tags, extra data (like componentStack), browser info, etc.
Source Maps (Crucial for Production): As mentioned in Part 1, for production errors, you need to upload source maps to Sentry.
- Build Process: Your build tool (Create React App, Webpack via Next.js, etc.) generates source maps (e.g.,
.js.map
files) during a production build. - Sentry CLI or Webpack Plugin:
- Sentry provides
@sentry/cli
and@sentry/webpack-plugin
. - These tools can be integrated into your build/deployment pipeline to automatically:
- Create a new "release" in Sentry (e.g.,
[email protected]
). - Upload the source maps for that release.
- Associate the release with the uploaded artifacts.
- Create a new "release" in Sentry (e.g.,
- Sentry provides
- Why? When Sentry receives an error from your minified production code, it uses the uploaded source maps for that release to reconstruct the original stack trace, showing you errors in your human-readable source code.
Example (Conceptual Sentry CLI usage in a build script):
# package.json scripts
# "build": "react-scripts build && npm run sentry:sourcemaps",
# "sentry:sourcemaps": "sentry-cli releases new $REACT_APP_SENTRY_RELEASE && \
# sentry-cli releases files $REACT_APP_SENTRY_RELEASE upload-sourcemaps ./build/static/js --url-prefix '~/static/js' && \
# sentry-cli releases finalize $REACT_APP_SENTRY_RELEASE"
(The exact commands and paths depend on your build output structure. Consult Sentry documentation.)
Without source maps, debugging production errors is extremely difficult.
💡 Conclusion & Key Takeaways (Part 2)
Integrating an error reporting service like Sentry into your React Error Boundaries transforms them from simple UI savers into powerful debugging tools. By sending detailed error reports with context to a centralized service, you gain vital insights into how your application behaves in the wild, enabling you to proactively address issues and improve stability.
Key Takeaways:
- Initialize the error reporting SDK (e.g., Sentry) early in your app.
- Use
Sentry.captureException(error)
incomponentDidCatch
(custom EB) or theonError
prop (react-error-boundary
). - Enrich reports with user context, tags, and extra data using
Sentry.setUser
,scope.setTag
,scope.setExtra
. - Uploading source maps to your error reporting service is essential for debugging production errors.
- Test your integration thoroughly to ensure errors are captured and displayed correctly in your service's dashboard.
This completes our series on Error Boundaries! You are now equipped to not only catch errors gracefully but also to effectively report and analyze them.
➡️ Next Steps
Congratulations on mastering Error Boundaries and error reporting! This is the end of Chapter 5: Performance and Optimization.
We now move to a new chapter: Chapter 06: 🚢 From Development to Production. The first series in this new chapter is Series 19: Testing Your React Application, and we'll kick it off with "The Testing Pyramid and React". Understanding how to test your application effectively is crucial before deploying it to production.
Onwards to building even more robust and reliable React applications!
glossary
- SDK (Software Development Kit): A set of tools, libraries, and documentation provided by a service (like Sentry) to help developers integrate that service into their applications.
- DSN (Data Source Name): A unique identifier provided by Sentry that tells the SDK where to send error data.
- Transaction (in Sentry): Represents a single instance of an operation that Sentry is monitoring for performance (e.g., a page load, a navigation change).
- Release (in Sentry): A specific version of your deployed application, allowing errors to be associated with the code version they occurred in.
- Source Map Upload: The process of sending your application's source map files to an error reporting service so it can un-minify production stack traces.
- User Feedback Widget: A UI element provided by some error reporting services (like Sentry) that can be shown to users when an error occurs, allowing them to submit additional details about the problem.
Further Reading
- Sentry React SDK Documentation
- Sentry Docs: Source Maps
- Sentry Docs: Enriching Events (Context, Tags, User)
@sentry/webpack-plugin
Documentationsentry-cli
Documentation