On a spacecraft, every failure is recorded in the black box. Without it, the crew couldn't analyze the causes of problems after the fact. In React applications, we have exactly the same problem -- if an error occurs for a user in production and we don't log it, we'll never know about it. Error logging and monitoring is your production black box.
In a production environment, we need several layers of logging:
console.error is useful during local development, but in production the user doesn't see it, and you don't have access to it.1// Good for development, useless in production
2componentDidCatch(error, errorInfo) {
3 console.error('Error caught:', error);
4 console.error('Component stack:', errorInfo.componentStack);
5}In production, errors must be sent to a monitoring service:
1class ErrorBoundary extends React.Component {
2 componentDidCatch(error, errorInfo) {
3 // Send to monitoring service
4 logErrorToService({
5 message: error.message,
6 stack: error.stack,
7 componentStack: errorInfo.componentStack,
8 timestamp: new Date().toISOString(),
9 url: window.location.href,
10 userAgent: navigator.userAgent,
11 });
12 }
13
14 // ...render with fallback
15}
16
17async function logErrorToService(errorData) {
18 try {
19 await fetch('/api/errors', {
20 method: 'POST',
21 headers: { 'Content-Type': 'application/json' },
22 body: JSON.stringify(errorData),
23 });
24 } catch (e) {
25 // Fallback -- don't block the app if logging fails
26 console.error('Failed to log error:', e);
27 }
28}Sentry is the most popular service for monitoring errors in JavaScript and React applications. Other popular tools include LogRocket, Bugsnag, Datadog, and New Relic.
1import * as Sentry from '@sentry/react';
2
3// Initialization in main/index.js
4Sentry.init({
5 dsn: 'https://key@sentry.io/project',
6 environment: 'production',
7 // Percentage of sessions to record (performance)
8 tracesSampleRate: 0.2,
9});
10
11// Sentry automatically captures:
12// - Unhandled exceptions
13// - Unhandled Promise rejections
14// - Network errors1import * as Sentry from '@sentry/react';
2
3// Sentry provides a ready-made Error Boundary
4function App() {
5 return (
6 <Sentry.ErrorBoundary
7 fallback={({ error }) => (
8 <div className="error-page">
9 <h2>Something went wrong</h2>
10 <p>The error has been automatically reported</p>
11 <button onClick={() => window.location.reload()}>
12 Refresh page
13 </button>
14 </div>
15 )}
16 >
17 <SpaceStation />
18 </Sentry.ErrorBoundary>
19 );
20}Sentry allows you to add context to errors -- information about the user, recent actions (breadcrumbs), and additional data:
1// Set user context
2Sentry.setUser({
3 id: user.id,
4 email: user.email,
5 username: user.callsign,
6});
7
8// Manually add breadcrumbs (user action trail)
9Sentry.addBreadcrumb({
10 category: 'navigation',
11 message: 'User navigated to mission panel',
12 level: 'info',
13});
14
15// Manually report an error with additional context
16Sentry.captureException(error, {
17 extra: {
18 missionId: currentMission.id,
19 moduleState: moduleStatus,
20 },
21});You can create your own hook that centralizes logging logic:
1function useErrorLogger() {
2 const logError = useCallback((error, context = {}) => {
3 const errorReport = {
4 message: error.message,
5 stack: error.stack,
6 timestamp: new Date().toISOString(),
7 url: window.location.href,
8 ...context,
9 };
10
11 // In production, send to the service
12 if (process.env.NODE_ENV === 'production') {
13 sendToErrorService(errorReport);
14 }
15
16 // In development, log to console
17 console.error('[ErrorLogger]', errorReport);
18 }, []);
19
20 return { logError };
21}
22
23// Usage in a component
24function MissionControl() {
25 const { logError } = useErrorLogger();
26
27 const handleDataFetch = async () => {
28 try {
29 const data = await fetchMissionData();
30 setMissions(data);
31 } catch (error) {
32 logError(error, {
33 component: 'MissionControl',
34 action: 'fetchMissionData',
35 });
36 setError('Failed to fetch mission data');
37 }
38 };
39}In addition to Error Boundaries and try-catch, it's worth adding global handlers that catch errors escaping other mechanisms:
1// Global catching of uncaught errors
2window.addEventListener('error', (event) => {
3 logErrorToService({
4 type: 'uncaught_error',
5 message: event.message,
6 filename: event.filename,
7 lineno: event.lineno,
8 colno: event.colno,
9 });
10});
11
12// Global catching of unhandled Promise rejections
13window.addEventListener('unhandledrejection', (event) => {
14 logErrorToService({
15 type: 'unhandled_rejection',
16 message: event.reason?.message || 'Unknown rejection',
17 stack: event.reason?.stack,
18 });
19});Not all errors are equally important. Error tracking services allow classification:
1// Critical error - application is non-functional
2Sentry.captureException(error, { level: 'fatal' });
3
4// Error - functionality is broken, but app works
5Sentry.captureException(error, { level: 'error' });
6
7// Warning - something might be wrong
8Sentry.captureMessage('API responding slowly', 'warning');
9
10// Info - useful for debugging
11Sentry.captureMessage('User changed language', 'info');Production code is minified -- the stack trace is unreadable without source maps. Source maps are like a decoding map -- they allow you to translate positions in minified code back to the original source files.
1// Without source maps, the stack trace looks like:
2// Error at e.render (main.a1b2c3.js:1:45678)
3
4// With source maps:
5// Error at MissionControl.render (MissionControl.jsx:42:15)1// webpack.config.js
2module.exports = {
3 devtool: 'source-map', // Generate source maps
4 // ...
5};
6
7// Important: source maps should NOT be public!
8// Upload them to Sentry, but don't serve them to users:
9// sentry-cli sourcemaps upload ./buildSource maps allow you to see exactly which line of original code caused the error, even if the production bundle is minified into a single line. It's like having the technical blueprint of a spacecraft -- instead of looking at an incomprehensible minified panel, you see exactly which module failed and why. Remember, however, to never make source maps publicly available -- that would be like sharing the ship's blueprints with potential invaders.