<h2>O que é Observabilidade em React?</h2>
<p>Observabilidade é a capacidade de entender o que está acontecendo dentro de uma aplicação através de seus dados gerados externamente. Em React, isso significa monitorar erros, performance, comportamento de usuários e eventos para diagnosticar problemas rapidamente. Diferente de testes unitários que validam o comportamento esperado, observabilidade captura o que realmente ocorre em produção com usuários reais.</p>
<p>Quando você deploya uma aplicação React, diversas coisas podem dar errado: crashes silenciosos em navegadores diferentes, erros assíncronos não capturados, performance degradada por componentes pesados, ou comportamentos inesperados em sessões específicas. Sem observabilidade, você fica cego. Com ela, você tem logs estruturados, stack traces completos, sessões gravadas e métricas de performance que revelam exatamente onde e por que os problemas ocorrem.</p>
<h2>Error Boundaries: A Primeira Linha de Defesa</h2>
<h3>Compreendendo Error Boundaries</h3>
<p>Error Boundaries são componentes React que capturam erros JavaScript ocorridos em qualquer lugar da árvore de componentes filhos. Eles funcionam como um try/catch para toda a árvore de componentes abaixo dele. Quando um erro ocorre durante a renderização, em métodos de ciclo de vida ou em construtores de componentes filhos, o Error Boundary captura esse erro e pode renderizar uma UI de fallback em vez de deixar a aplicação quebrar completamente.</p>
<p>É importante entender que Error Boundaries <strong>não</strong> capturam erros em event handlers (use try/catch direto), em código assíncrono fora do React (como Promises), ou em renderização do próprio Error Boundary. Eles são especializados em erros de ciclo de vida de renderização.</p>
<h3>Implementando um Error Boundary</h3>
<pre><code class="language-javascript">import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
this.setState({
error: error,
errorInfo: errorInfo
});
// Aqui você enviaria o erro para Sentry ou LogRocket
console.error('Erro capturado:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '20px', color: 'red' }}>
<h1>Algo deu errado</h1>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo && this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;</code></pre>
<p>Este componente de classe implementa os dois métodos que tornam um Error Boundary funcional: <code>getDerivedStateFromError</code> (atualiza o estado quando um erro é detectado) e <code>componentDidCatch</code> (executa lógica como logging quando o erro é capturado). O padrão <code>getDerivedStateFromError</code> é estático e puro, enquanto <code>componentDidCatch</code> permite side effects.</p>
<h3>Usando Error Boundaries na Prática</h3>
<pre><code class="language-javascript">import ErrorBoundary from './ErrorBoundary';
import Dashboard from './pages/Dashboard';
import UserProfile from './pages/UserProfile';
function App() {
return (
<ErrorBoundary>
<div className="app">
<header>
<h1>Minha Aplicação</h1>
</header>
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
</div>
</ErrorBoundary>
);
}
export default App;</code></pre>
<p>Você pode usar múltiplos Error Boundaries em diferentes partes da aplicação. Isso é uma estratégia excelente porque um erro em um componente profundo não quebrará toda a aplicação—apenas a seção protegida por aquele Error Boundary será afetada. O erro em Dashboard não afetará UserProfile.</p>
<h2>Sentry: Monitoramento Profissional de Erros</h2>
<h3>O que é Sentry e por que usar?</h3>
<p>Sentry é uma plataforma de monitoramento de erros em tempo real que captura, agrupa e alerta sobre exceções em produção. Ele fornece stack traces completos, contexto de sessão, histórico de versões (releases) e integrações com seus ferramentas de desenvolvimento. Diferente de logs locais, Sentry centraliza todos os erros em um dashboard onde você pode filtrar, agrupar por padrão e priorizar correções.</p>
<p>A principal vantagem é que Sentry não apenas captura o erro—ele captura o contexto: qual usuário foi afetado, qual página estava visitando, qual navegador usava, qual versão do seu código estava rodando. Isso transforma observabilidade em ação.</p>
<h3>Instalação e Configuração do Sentry</h3>
<pre><code class="language-bash">npm install @sentry/react @sentry/tracing</code></pre>
<p>A instalação é simples. Agora configure-o no ponto de entrada da sua aplicação:</p>
<pre><code class="language-javascript">// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";
import App from './App';
Sentry.init({
dsn: "https://seu_dsn@sentry.io/seu_project_id",
integrations: [
new BrowserTracing(),
new Sentry.Replay({
maskAllText: true,
blockAllMedia: true,
}),
],
tracesSampleRate: 0.1,
release: "1.0.0",
environment: process.env.NODE_ENV,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Sentry.ErrorBoundary fallback={<p>Algo deu errado</p>}>
<App />
</Sentry.ErrorBoundary>
</React.StrictMode>
);</code></pre>
<p>O <code>dsn</code> (Data Source Name) é único para seu projeto no Sentry. Você o encontra nas configurações do projeto. <code>tracesSampleRate: 0.1</code> significa que apenas 10% das requisições serão rastreadas (ajuste conforme necessário). <code>replaysOnErrorSampleRate: 1.0</code> garante que 100% das sessões com erro sejam gravadas para replay.</p>
<h3>Capturando Erros Explicitamente</h3>
<pre><code class="language-javascript">import * as Sentry from "@sentry/react";
function processPayment(amount) {
try {
if (amount <= 0) {
throw new Error('Valor de pagamento inválido');
}
// lógica de pagamento
return { success: true };
} catch (error) {
Sentry.captureException(error, {
contexts: {
payment: {
amount: amount,
timestamp: new Date().toISOString(),
}
},
level: 'error'
});
throw error;
}
}</code></pre>
<p>Use <code>Sentry.captureException()</code> em event handlers e código assíncrono onde Error Boundaries não alcançam. O objeto de contexto adiciona dados estruturados que aparecem no painel do Sentry, facilitando investigação.</p>
<h3>Adicionando Contexto de Usuário</h3>
<pre><code class="language-javascript">import * as Sentry from "@sentry/react";
function setUserContext(user) {
Sentry.setUser({
id: user.id,
email: user.email,
username: user.username,
ip_address: "{{auto}}", // Sentry captura automaticamente
});
}
function clearUserContext() {
Sentry.setUser(null);
}</code></pre>
<p>Após login, chame <code>setUserContext()</code> para associar todos os erros subsequentes a esse usuário específico. Isso é crítico para rastrear qual usuário enfrentou cada problema.</p>
<h2>LogRocket: Gravação de Sessão e Analytics</h2>
<h3>Entendendo LogRocket</h3>
<p>LogRocket é diferente do Sentry. Enquanto Sentry é focado em erros, LogRocket grava cada sessão de usuário como um vídeo—você pode ver exatamente o que o usuário fez antes de um erro ocorrer. Ele captura ações DOM, console logs, network requests e estado Redux. É especialmente poderoso para bugs que só ocorrem em condições específicas.</p>
<p>LogRocket complementa Sentry: Sentry responde "o que deu errado?", LogRocket responde "como chegou a dar errado?". Juntos, formam uma observabilidade completa.</p>
<h3>Instalação e Configuração do LogRocket</h3>
<pre><code class="language-bash">npm install logrocket</code></pre>
<p>Configure no início da aplicação, antes de qualquer outro código:</p>
<pre><code class="language-javascript">// src/index.js
import LogRocket from 'logrocket';
import * as Sentry from "@sentry/react";
LogRocket.init('seu-app-id/seu-project');
// Integração com Sentry
LogRocket.getSessionURL(sessionURL => {
Sentry.captureMessage("LogRocket Session", {
level: "info",
contexts: {
react: {
session_url: sessionURL
}
}
});
});</code></pre>
<p>O <code>app-id</code> é fornecido pelo LogRocket na criação do projeto. Essa integração garante que quando um erro é capturado pelo Sentry, o URL da sessão LogRocket aparece no painel Sentry, permitindo reproduzir exatamente o que o usuário fez.</p>
<h3>Capturando Eventos Customizados</h3>
<pre><code class="language-javascript">import LogRocket from 'logrocket';
function handleCheckout(cartItems) {
LogRocket.captureMessage('Checkout iniciado', {
level: 'info',
payload: {
itemCount: cartItems.length,
total: cartItems.reduce((sum, item) => sum + item.price, 0)
}
});
// lógica de checkout
}
function handleError(errorCode, context) {
LogRocket.captureException(new Error(Erro: ${errorCode}), {
contexts: {
error_details: context
}
});
}</code></pre>
<p>Use <code>captureMessage()</code> para eventos importantes que não são erros—conversões, ações chave, mudanças de estado. Use <code>captureException()</code> para erros. LogRocket agrega esses eventos com a gravação de sessão.</p>
<h3>Identificação de Usuários</h3>
<pre><code class="language-javascript">import LogRocket from 'logrocket';
function handleLogin(user) {
LogRocket.identify(user.id, {
name: user.name,
email: user.email,
subscription: user.plan,
created_at: user.createdAt
});
}
function handleLogout() {
LogRocket.identify(null);
}</code></pre>
<p>Após autenticação, identifique o usuário para que LogRocket associe a sessão gravada a esse usuário específico. Isso permite buscar sessões por usuário no painel.</p>
<h2>Combinando Error Boundaries, Sentry e LogRocket</h2>
<h3>Estratégia Integrada de Observabilidade</h3>
<pre><code class="language-javascript">// src/ErrorBoundary.js
import React from 'react';
import * as Sentry from "@sentry/react";
import LogRocket from 'logrocket';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Sentry captura o erro
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack
}
}
});
// LogRocket captura a sessão
LogRocket.captureException(error, {
contexts: {
errorInfo
}
});
// Log local para desenvolvimento
console.error('Error Boundary capturou:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '40px', textAlign: 'center' }}>
<h2>Oops! Algo deu errado.</h2>
<p>Nosso time foi notificado. Por favor, recarregue a página.</p>
<button onClick={() => window.location.reload()}>
Recarregar
</button>
</div>
);
}
return this.props.children;
}
}
export default Sentry.withProfiler(ErrorBoundary);</code></pre>
<p><code>Sentry.withProfiler()</code> envolve o componente para rastrear performance de renderização automaticamente. Agora o Error Boundary não só captura erros—ele envia métricas de performance ao Sentry também.</p>
<h3>Inicialização Completa da Aplicação</h3>
<pre><code class="language-javascript">// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import LogRocket from 'logrocket';
import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";
import App from './App';
import ErrorBoundary from './ErrorBoundary';
// LogRocket primeiro (não deve ser after Sentry init)
LogRocket.init('seu-app-id/seu-project', {
console: {
shouldAggregateConsoleErrors: true
},
network: {
requestSanitizer: (request) => {
request.headers['Authorization'] = '[Redacted]';
return request;
}
}
});
// Sentry após LogRocket
Sentry.init({
dsn: "https://seu_dsn@sentry.io/seu_project_id",
integrations: [
new BrowserTracing(),
new Sentry.Replay({
maskAllText: true,
blockAllMedia: true,
}),
],
tracesSampleRate: 0.1,
release: "1.0.0",
environment: process.env.NODE_ENV,
});
// Integração: LogRocket URL no Sentry
LogRocket.getSessionURL(sessionURL => {
Sentry.configureScope(scope => {
scope.setContext('LogRocket', {
sessionURL: sessionURL
});
});
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<ErrorBoundary>
<App />
</ErrorBoundary>
</React.StrictMode>
);</code></pre>
<p>A ordem importa: LogRocket deve ser inicializado antes de Sentry. Essa configuração cria uma malha de proteção: Error Boundaries pegam erros de renderização, Sentry envia relatórios estruturados, LogRocket grava comportamento de usuários.</p>
<h3>Exemplo Prático: Componente com Tratamento Completo</h3>
<pre><code class="language-javascript">// src/pages/PaymentForm.js
import React, { useState } from 'react';
import * as Sentry from "@sentry/react";
import LogRocket from 'logrocket';
function PaymentForm() {
const [amount, setAmount] = useState('');
const [loading, setLoading] = useState(false);
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
try {
LogRocket.captureMessage('Formulário de pagamento submetido', {
level: 'info',
payload: { amount: parseFloat(amount) }
});
const response = await fetch('/api/payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: parseFloat(amount) })
});
if (!response.ok) {
throw new Error(Erro HTTP: ${response.status});
}
const data = await response.json();
LogRocket.captureMessage('Pagamento bem-sucedido', {
level: 'info'
});
} catch (error) {
// Sentry captura erro com contexto
Sentry.captureException(error, {
level: 'error',
contexts: {
payment: {
amount: parseFloat(amount),
endpoint: '/api/payment'
}
}
});
// LogRocket captura a exceção também
LogRocket.captureException(error);
alert('Erro no pagamento. Por favor, tente novamente.');
} finally {
setLoading(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Valor em R$"
disabled={loading}
/>
<button type="submit" disabled={loading}>
{loading ? 'Processando...' : 'Pagar'}
</button>
</form>
);
}
export default Sentry.withProfiler(PaymentForm);</code></pre>
<p>Este componente demonstra a prática completa: Error Boundaries protegem componentes, try/catch em event handlers/async, Sentry e LogRocket capturando eventos, e <code>withProfiler</code> rastreando performance.</p>
<h2>Conclusão</h2>
<p>Observabilidade em React não é um luxo—é uma necessidade em produção. Error Boundaries funcionam como guardrails que impedem crashes totais, Sentry fornece inteligência sobre erros com alertas automáticos e integração com ferramentas de desenvolvimento, e LogRocket oferece reprodução de sessão que torna a depuração de bugs complexos uma tarefa possível. Quando usados juntos de forma estratégica, esses três componentes criam uma visibilidade completa do seu aplicativo: você sabe quando algo deu errado (Sentry), por que deu errado (LogRocket), e o aplicativo continua funcionando (Error Boundaries). A chave é configurá-los cedo no projeto e instrumentar pontos críticos—não espere até que um cliente reporte um problema em produção.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.sentry.io/platforms/javascript/guides/react/" target="_blank" rel="noopener noreferrer">Sentry React Documentation</a></li>
<li><a href="https://docs.logrocket.com/reference/setup" target="_blank" rel="noopener noreferrer">LogRocket Setup Guide</a></li>
<li><a href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary" target="_blank" rel="noopener noreferrer">React Error Boundary Documentation</a></li>
<li><a href="https://web.dev/vitals/" target="_blank" rel="noopener noreferrer">Google Web Vitals & Performance Monitoring</a></li>
<li><a href="https://artofmonitoring.com/" target="_blank" rel="noopener noreferrer">The Art of Monitoring - James Turnbull</a></li>
</ul>
<p><!-- FIM --></p>