Instrumentação Frontend com Grafana Faro Web SDK
O Collector FE é implantado na infraestrutura do cliente e recebe os dados do Faro SDK via HTTP, encaminhando para o Grafana Loki. Consulte a documentação do Collector FE para detalhes de deploy.
Índice
- O que o Faro captura
- Instalação
- Configuração base
- React
- React Router v7 (Data Router)
- React Router v6 (Data Router)
- ErrorBoundary
- Component Profiling
- Identificação do usuário logado
- Next.js
- App Router (Next.js 14+)
- Pages Router
- Angular
- Vue.js
- Vanilla JavaScript / HTML
- OpenTelemetry Tracing
- Sinais customizados
- Eventos
- Logs
- Medições
- User Actions
- Session Tracking
- Web Vitals
- Identificação de usuário
- View Tracking
- Sampling
- CORS e segurança
- Variáveis de ambiente por framework
- Boas práticas
- Troubleshooting
- FAQ
O que o Faro captura
O Faro Web SDK v2 coleta automaticamente:
| Sinal | Descrição | Exemplo |
|---|---|---|
| Web Vitals | LCP, INP, CLS, FCP, TTFB (Web Vitals v5) | LCP = 2.1s |
| Erros JavaScript | Exceções não tratadas, com stacktrace | TypeError: Cannot read property 'x' of undefined |
| Console | Interceptação de console.log/warn/error/info |
console.error('Falha no checkout') |
| Navegação | Mudanças de rota (SPA) e page loads | /home → /checkout |
| Sessões | Tracking de sessão com lifecycle events | session_start, session_resume |
| Eventos customizados | Interações de negócio | Clique em "Comprar", busca realizada |
| Logs customizados | Logs estruturados enviados manualmente | Debug info, warnings |
| Medições | Métricas de performance da aplicação | Tempo de renderização, tempo de busca |
| User Actions | Interações do usuário com spans automáticos | Click, submit, drag |
| Traces | Distributed tracing via OpenTelemetry-JS | Fetch requests com trace context |
Instalação
NPM / Yarn / pnpm
# SDK base (obrigatório)
npm install @grafana/faro-web-sdk
# Tracing com OpenTelemetry (recomendado)
npm install @grafana/faro-web-tracing
# React (se aplicável)
npm install @grafana/faro-react
O pacote @grafana/faro-react já inclui tudo de @grafana/faro-web-sdk, então para projetos React basta instalar @grafana/faro-react + @grafana/faro-web-tracing.
CDN (sem bundler)
<script src="https://unpkg.com/@grafana/faro-web-sdk@^2/dist/bundle/faro-web-sdk.iife.js"></script>
<script>
window.GrafanaFaroWebSdk.initializeFaro({
url: 'https://collector-fe.meusite.com.br/collect/meu-tenant/eyJhbGci...',
app: { name: 'minha-app', version: '1.0.0', environment: 'production' },
});
</script>
Configuração base
Crie um arquivo dedicado para o Faro e importe-o antes de qualquer outro código no entry point da aplicação.
src/faro.ts
import { initializeFaro, getWebInstrumentations } from '@grafana/faro-web-sdk';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
export const faro = initializeFaro({
url: import.meta.env.VITE_FARO_URL,
app: {
name: 'minha-app',
version: import.meta.env.VITE_APP_VERSION || '0.0.0',
environment: import.meta.env.MODE, // 'development' | 'production'
},
sessionTracking: {
enabled: true,
persistent: true, // mantém sessão entre reloads (localStorage)
samplingRate: 1, // 1 = 100% das sessões
},
batching: {
enabled: true,
sendTimeout: 250, // ms entre batches
itemLimit: 50, // itens por batch
},
dedupe: {
enabled: true,
interval: 1000, // ignora sinais duplicados em 1s
},
instrumentations: [
...getWebInstrumentations({
captureConsole: true,
captureConsoleDisabledLevels: [], // captura todos os níveis
}),
new TracingInstrumentation({
instrumentationOptions: {
propagateTraceHeaderCorsUrls: [
new RegExp(`${import.meta.env.VITE_API_URL}/*`),
],
},
}),
],
});
URL do Collector FE
A URL segue o formato:
https://<host-collector>/collect/<tenant>/<jwt-token>
O <tenant> identifica o produto/cliente no Loki, e o <jwt-token> é o JWT de autenticação. Consulte a documentação do Collector FE para gerar o token.
React
React Router v7 (Data Router)
// src/faro.ts
import {
initializeFaro,
getWebInstrumentations,
ReactIntegration,
createReactRouterV7DataOptions,
} from '@grafana/faro-react';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
import { matchRoutes } from 'react-router';
export const faro = initializeFaro({
url: import.meta.env.VITE_FARO_URL,
app: {
name: 'minha-app-react',
version: import.meta.env.VITE_APP_VERSION || '0.0.0',
environment: import.meta.env.MODE,
},
sessionTracking: {
enabled: true,
persistent: true,
samplingRate: 1,
},
instrumentations: [
...getWebInstrumentations({ captureConsole: true }),
new TracingInstrumentation({
instrumentationOptions: {
propagateTraceHeaderCorsUrls: [/https:\/\/api\.meusite\.com\.br\/.*/],
},
}),
new ReactIntegration({
router: createReactRouterV7DataOptions({ matchRoutes }),
}),
],
});
// src/router.ts
import { createBrowserRouter } from 'react-router';
import { withFaroRouterInstrumentation } from '@grafana/faro-react';
const baseRouter = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{ index: true, element: <Home /> },
{ path: 'products/:id', element: <ProductDetail /> },
{ path: 'checkout', element: <Checkout /> },
],
},
]);
export const router = withFaroRouterInstrumentation(baseRouter);
// src/main.tsx
import './faro'; // Importar ANTES de tudo
import { RouterProvider } from 'react-router';
import { router } from './router';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
);
React Router v6 (Data Router)
// src/faro.ts
import {
initializeFaro,
getWebInstrumentations,
ReactIntegration,
createReactRouterV6DataOptions,
} from '@grafana/faro-react';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
import { matchRoutes } from 'react-router-dom';
export const faro = initializeFaro({
url: import.meta.env.VITE_FARO_URL,
app: {
name: 'minha-app-react',
version: import.meta.env.VITE_APP_VERSION || '0.0.0',
environment: import.meta.env.MODE,
},
instrumentations: [
...getWebInstrumentations({ captureConsole: true }),
new TracingInstrumentation(),
new ReactIntegration({
router: createReactRouterV6DataOptions({ matchRoutes }),
}),
],
});
// src/router.ts
import { createBrowserRouter } from 'react-router-dom';
import { withFaroRouterInstrumentation } from '@grafana/faro-react';
const baseRouter = createBrowserRouter([/* routes */]);
export const router = withFaroRouterInstrumentation(baseRouter);
ErrorBoundary
O @grafana/faro-react inclui um ErrorBoundary que captura erros de renderização e envia automaticamente para o Faro:
import { FaroErrorBoundary } from '@grafana/faro-react';
function App() {
return (
<FaroErrorBoundary
fallback={(error, resetError) => (
<div>
<h2>Algo deu errado</h2>
<p>{error.message}</p>
<button onClick={resetError}>Tentar novamente</button>
</div>
)}
>
<RouterProvider router={router} />
</FaroErrorBoundary>
);
}
Pode ser usado em qualquer nível da árvore de componentes para capturar erros granularmente:
<FaroErrorBoundary fallback={<ErrorFallback />}>
<CheckoutForm />
</FaroErrorBoundary>
<FaroErrorBoundary fallback={<ErrorFallback />}>
<ProductRecommendations />
</FaroErrorBoundary>
Component Profiling
Meça o tempo de renderização de componentes específicos:
import { withFaroProfiler } from '@grafana/faro-react';
function ProductList({ products }: Props) {
return (
<ul>
{products.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
export default withFaroProfiler(ProductList);
Os dados de profiling são enviados como measurements com o tipo component_render e incluem tempos de mount e update.
Identificação do usuário logado
Sincronize o usuário autenticado com o Faro para correlacionar sessões com usuários reais:
// src/components/FaroUserSync.tsx
import { useEffect } from 'react';
import { faro } from '../faro';
interface User {
id: string;
name: string;
email: string;
role?: string;
}
export function FaroUserSync({ user }: { user: User | null }) {
useEffect(() => {
if (user) {
faro.api.setUser({
id: user.id,
username: user.name,
email: user.email,
attributes: {
role: user.role ?? 'user',
},
});
} else {
faro.api.resetUser();
}
}, [user]);
return null;
}
// src/App.tsx
import { FaroUserSync } from './components/FaroUserSync';
import { useAuth } from './hooks/useAuth';
function App() {
const { user } = useAuth();
return (
<>
<FaroUserSync user={user} />
<RouterProvider router={router} />
</>
);
}
Next.js
App Router (Next.js 14+)
Com o App Router, o Faro deve ser inicializado apenas no client side:
// src/lib/faro.ts
'use client';
import { initializeFaro, getWebInstrumentations } from '@grafana/faro-web-sdk';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
let faroInstance: ReturnType<typeof initializeFaro> | null = null;
export function getFaro() {
if (typeof window === 'undefined') return null;
if (faroInstance) return faroInstance;
faroInstance = initializeFaro({
url: process.env.NEXT_PUBLIC_FARO_URL!,
app: {
name: process.env.NEXT_PUBLIC_APP_NAME || 'minha-app-next',
version: process.env.NEXT_PUBLIC_APP_VERSION || '0.0.0',
environment: process.env.NODE_ENV,
},
sessionTracking: {
enabled: true,
persistent: true,
samplingRate: 1,
},
instrumentations: [
...getWebInstrumentations({ captureConsole: true }),
new TracingInstrumentation({
instrumentationOptions: {
propagateTraceHeaderCorsUrls: [
new RegExp(`${process.env.NEXT_PUBLIC_API_URL}/*`),
],
},
}),
],
});
return faroInstance;
}
// src/components/FaroProvider.tsx
'use client';
import { useEffect } from 'react';
import { getFaro } from '@/lib/faro';
export function FaroProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
getFaro();
}, []);
return <>{children}</>;
}
// src/app/layout.tsx
import { FaroProvider } from '@/components/FaroProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pt-BR">
<body>
<FaroProvider>
{children}
</FaroProvider>
</body>
</html>
);
}
Sincronizar usuário logado (Next.js + NextAuth/Auth.js):
// src/components/FaroUserSync.tsx
'use client';
import { useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { getFaro } from '@/lib/faro';
export function FaroUserSync() {
const { data: session } = useSession();
useEffect(() => {
const faro = getFaro();
if (!faro) return;
if (session?.user) {
faro.api.setUser({
id: session.user.id ?? '',
username: session.user.name ?? '',
email: session.user.email ?? '',
});
} else {
faro.api.resetUser();
}
}, [session]);
return null;
}
Pages Router
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { useEffect } from 'react';
import { initializeFaro, getWebInstrumentations } from '@grafana/faro-web-sdk';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
let initialized = false;
export default function MyApp({ Component, pageProps }: AppProps) {
useEffect(() => {
if (initialized) return;
initialized = true;
initializeFaro({
url: process.env.NEXT_PUBLIC_FARO_URL!,
app: {
name: 'minha-app-next',
version: process.env.NEXT_PUBLIC_APP_VERSION || '0.0.0',
environment: process.env.NODE_ENV,
},
sessionTracking: { enabled: true, persistent: true },
instrumentations: [
...getWebInstrumentations({ captureConsole: true }),
new TracingInstrumentation(),
],
});
}, []);
return <Component {...pageProps} />;
}
Angular
O Faro não tem um pacote específico para Angular, mas integra-se usando o SDK base com um service:
npm install @grafana/faro-web-sdk @grafana/faro-web-tracing
// src/app/services/faro.service.ts
import { Injectable } from '@angular/core';
import { Faro, initializeFaro, getWebInstrumentations } from '@grafana/faro-web-sdk';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
import { environment } from '../../environments/environment';
@Injectable({ providedIn: 'root' })
export class FaroService {
private faro: Faro | null = null;
constructor(private router: Router) {}
initialize(): void {
this.faro = initializeFaro({
url: environment.faroUrl,
app: {
name: environment.appName,
version: environment.appVersion,
environment: environment.production ? 'production' : 'development',
},
sessionTracking: {
enabled: true,
persistent: true,
samplingRate: 1,
},
instrumentations: [
...getWebInstrumentations({ captureConsole: true }),
new TracingInstrumentation({
instrumentationOptions: {
propagateTraceHeaderCorsUrls: [new RegExp(`${environment.apiUrl}/*`)],
},
}),
],
});
this.trackRouteChanges();
}
private trackRouteChanges(): void {
this.router.events
.pipe(filter((event): event is NavigationEnd => event instanceof NavigationEnd))
.subscribe((event) => {
this.faro?.api.pushEvent('navigation', {
route: event.urlAfterRedirects,
});
this.faro?.api.setView({ name: event.urlAfterRedirects });
});
}
setUser(user: { id: string; name: string; email: string }): void {
this.faro?.api.setUser({
id: user.id,
username: user.name,
email: user.email,
});
}
resetUser(): void {
this.faro?.api.resetUser();
}
pushEvent(name: string, attributes?: Record<string, string>): void {
this.faro?.api.pushEvent(name, attributes);
}
pushLog(message: string, level: 'info' | 'warn' | 'error' = 'info'): void {
this.faro?.api.pushLog([message], { level: level as any });
}
get instance(): Faro | null {
return this.faro;
}
}
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { FaroService } from './services/faro.service';
@Component({
selector: 'app-root',
template: '<router-outlet />',
})
export class AppComponent implements OnInit {
constructor(private faroService: FaroService) {}
ngOnInit(): void {
this.faroService.initialize();
}
}
ErrorHandler global para capturar erros Angular:
// src/app/services/faro-error-handler.ts
import { ErrorHandler, Injectable } from '@angular/core';
import { FaroService } from './faro.service';
@Injectable()
export class FaroErrorHandler implements ErrorHandler {
constructor(private faroService: FaroService) {}
handleError(error: Error): void {
const faro = this.faroService.instance;
if (faro) {
faro.api.pushError(error);
}
console.error(error);
}
}
// src/app/app.module.ts
import { ErrorHandler, NgModule } from '@angular/core';
import { FaroErrorHandler } from './services/faro-error-handler';
@NgModule({
providers: [
{ provide: ErrorHandler, useClass: FaroErrorHandler },
],
})
export class AppModule {}
Vue.js
npm install @grafana/faro-web-sdk @grafana/faro-web-tracing
// src/plugins/faro.ts
import { initializeFaro, getWebInstrumentations, type Faro } from '@grafana/faro-web-sdk';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
import type { App, Plugin } from 'vue';
import type { Router } from 'vue-router';
let faroInstance: Faro | null = null;
export const faroPlugin: Plugin = {
install(app: App, options: { url: string; appName: string; version: string; router: Router }) {
faroInstance = initializeFaro({
url: options.url,
app: {
name: options.appName,
version: options.version,
environment: import.meta.env.MODE,
},
sessionTracking: { enabled: true, persistent: true },
instrumentations: [
...getWebInstrumentations({ captureConsole: true }),
new TracingInstrumentation(),
],
});
options.router.afterEach((to) => {
faroInstance?.api.setView({ name: to.path });
faroInstance?.api.pushEvent('navigation', { route: to.path });
});
app.config.errorHandler = (err, instance, info) => {
if (err instanceof Error) {
faroInstance?.api.pushError(err);
}
console.error(err);
};
app.provide('faro', faroInstance);
},
};
export function useFaro(): Faro | null {
return faroInstance;
}
// src/main.ts
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import { faroPlugin } from './plugins/faro';
const router = createRouter({
history: createWebHistory(),
routes: [/* ... */],
});
const app = createApp(App);
app.use(faroPlugin, {
url: import.meta.env.VITE_FARO_URL,
appName: 'minha-app-vue',
version: import.meta.env.VITE_APP_VERSION || '0.0.0',
router,
});
app.use(router);
app.mount('#app');
Vanilla JavaScript / HTML
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Minha App</title>
<script src="https://unpkg.com/@grafana/faro-web-sdk@^2/dist/bundle/faro-web-sdk.iife.js"></script>
<script>
var faro = window.GrafanaFaroWebSdk.initializeFaro({
url: 'https://collector-fe.meusite.com.br/collect/meu-tenant/eyJhbGci...',
app: {
name: 'minha-app',
version: '1.0.0',
environment: 'production',
},
sessionTracking: { enabled: true, persistent: true },
});
// Capturar erros globais
window.addEventListener('error', function(event) {
faro.api.pushError(new Error(event.message));
});
// Identificar usuário (chamar após login)
function setFaroUser(id, name, email) {
faro.api.setUser({ id: id, username: name, email: email });
}
</script>
</head>
<body>
<!-- Conteúdo da aplicação -->
</body>
</html>
OpenTelemetry Tracing
O tracing conecta o frontend ao backend, permitindo rastrear uma requisição do clique do usuário até o banco de dados.
Configuração com TracingInstrumentation
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
new TracingInstrumentation({
instrumentationOptions: {
// URLs para as quais enviar trace headers (W3C Trace Context)
propagateTraceHeaderCorsUrls: [
new RegExp('https://api\\.meusite\\.com\\.br/.*'),
'https://checkout-api.meusite.com.br',
],
// Opções do fetch instrumentation
fetchInstrumentationOptions: {
ignoreNetworkEvents: true, // reduz ruído de eventos de rede
},
},
})
Criar spans manuais
const { trace, context } = faro.api.getOTEL();
const tracer = trace.getTracer('minha-app');
// Span simples
const span = tracer.startSpan('checkout.process');
try {
await processCheckout(cart);
span.setStatus({ code: 1 }); // OK
} catch (error) {
span.setStatus({ code: 2, message: error.message }); // ERROR
span.recordException(error);
throw error;
} finally {
span.end();
}
Span com contexto propagado
const { trace, context } = faro.api.getOTEL();
const tracer = trace.getTracer('minha-app');
const parentSpan = tracer.startSpan('user.action');
context.with(trace.setSpan(context.active(), parentSpan), async () => {
// Todas as chamadas fetch dentro deste contexto herdam o trace
const response = await fetch('https://api.meusite.com.br/orders', {
method: 'POST',
body: JSON.stringify(orderData),
});
const childSpan = tracer.startSpan('parse.response');
const data = await response.json();
childSpan.end();
parentSpan.end();
});
Sinais customizados
Eventos
Eventos rastreiam interações de negócio e comportamento do usuário:
// Evento de conversão
faro.api.pushEvent('purchase_completed', {
orderId: 'ORD-12345',
total: '299.90',
items: '3',
paymentMethod: 'credit_card',
}, 'checkout');
// Evento de busca
faro.api.pushEvent('search_performed', {
query: 'camiseta azul',
resultsCount: '42',
page: '/busca',
}, 'search');
// Evento de feature toggle
faro.api.pushEvent('feature_flag_evaluated', {
flag: 'new-checkout-flow',
variant: 'treatment',
userId: 'usr-123',
}, 'experimentation');
// Evento de erro de formulário
faro.api.pushEvent('form_validation_failed', {
form: 'checkout',
fields: 'cpf,cep',
attempt: '2',
}, 'forms');
Logs
Logs estruturados com nível e contexto:
import { LogLevel } from '@grafana/faro-web-sdk';
// Info
faro.api.pushLog(['Checkout iniciado para o pedido ORD-12345'], {
level: LogLevel.INFO,
context: {
cartItems: '3',
totalValue: '299.90',
},
});
// Warning
faro.api.pushLog(['Estoque baixo para produto SKU-789'], {
level: LogLevel.WARN,
context: {
sku: 'SKU-789',
remaining: '2',
},
});
// Error
faro.api.pushLog(['Falha ao processar pagamento'], {
level: LogLevel.ERROR,
context: {
gateway: 'stripe',
errorCode: 'card_declined',
orderId: 'ORD-12345',
},
});
// Debug
faro.api.pushLog(['WebSocket reconectado após 3 tentativas'], {
level: LogLevel.DEBUG,
context: {
attempts: '3',
latencyMs: '1250',
},
});
Medições
Métricas de performance específicas da aplicação:
// Tempo de renderização de um componente
faro.api.pushMeasurement({
type: 'component_performance',
values: {
product_list_render_ms: 142.3,
product_count: 48,
},
});
// Tempo de resposta da API percebido pelo usuário
faro.api.pushMeasurement({
type: 'api_latency',
values: {
search_request_ms: 273,
results_render_ms: 45,
total_perceived_ms: 318,
},
});
// Métricas de cache
faro.api.pushMeasurement({
type: 'cache_stats',
values: {
hit_rate: 0.87,
size_mb: 12.4,
evictions: 3,
},
});
User Actions
O Faro v2 rastreia interações do usuário automaticamente, criando spans que agrupam todos os efeitos colaterais (fetch requests, DOM updates, etc.) de uma ação.
Automático via atributo HTML
<button data-faro-user-action-name="add-to-cart">
Adicionar ao carrinho
</button>
<form data-faro-user-action-name="checkout-submit">
<!-- campos -->
<button type="submit">Finalizar compra</button>
</form>
Programático
// Iniciar uma ação manual com controle total
const action = faro.api.startUserAction('complete-checkout', {
importance: 'critical', // 'normal' | 'critical'
});
try {
await submitOrder(cart);
action?.end(); // Finalizar com sucesso
} catch (error) {
action?.end();
faro.api.pushError(error);
}
User actions completam automaticamente 100ms após o último evento vinculado. Para requests pendentes, o timeout é configurável até 10 segundos.
Session Tracking
Sessões agrupam todas as interações de um usuário em uma visita.
Comportamento padrão
- Duração máxima: 4 horas
- Timeout de inatividade: 15 minutos
- Persistência: com
persistent: true, a sessão sobrevive a reloads da página (vialocalStorage) - Eventos de lifecycle:
session_start,session_resume,session_extend
Configuração
initializeFaro({
// ...
sessionTracking: {
enabled: true,
persistent: true, // sobrevive a page reloads
samplingRate: 1, // 1 = todas as sessões, 0.5 = 50%
},
});
Acessar o ID da sessão
const sessionId = faro.api.getSession()?.id;
O header x-faro-session-id é automaticamente incluído nas requests para o Collector FE, permitindo correlação com dados de backend.
Web Vitals
O Faro v2 captura automaticamente as Web Vitals v5:
| Métrica | Nome | Bom | Precisa melhorar | Ruim |
|---|---|---|---|---|
| LCP | Largest Contentful Paint | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP | Interaction to Next Paint | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS | Cumulative Layout Shift | ≤ 0.1 | ≤ 0.25 | > 0.25 |
| FCP | First Contentful Paint | ≤ 1.8s | ≤ 3.0s | > 3.0s |
| TTFB | Time to First Byte | ≤ 800ms | ≤ 1800ms | > 1800ms |
Faro v2: A métrica FID (First Input Delay) foi removida e substituída por INP (Interaction to Next Paint). A atribuição de Web Vitals é coletada por padrão.
Não é necessário nenhuma configuração adicional — as Web Vitals são capturadas automaticamente pelo getWebInstrumentations().
Identificação de usuário
Permite correlacionar sessões com usuários reais no Grafana:
// Após login
faro.api.setUser({
id: 'usr-12345',
username: 'João Silva',
email: 'joao@empresa.com.br',
attributes: {
role: 'admin',
plan: 'enterprise',
company: 'Acme Corp',
},
});
// Após logout
faro.api.resetUser();
Nunca envie dados sensíveis (CPF, senha, tokens) nos atributos do usuário.
View Tracking
Marque mudanças de "view" manualmente para SPAs que não usam router integrado:
// Ao navegar para uma nova seção
faro.api.setView({ name: '/dashboard' });
// Com atributos
faro.api.setView({
name: '/product/:id',
attributes: {
productId: '123',
category: 'electronics',
},
});
Para frameworks com integração de router (React Router, Next.js), o view tracking é automático.
Sampling
Controle o volume de dados enviados:
initializeFaro({
// ...
sessionTracking: {
samplingRate: 0.1, // Captura 10% das sessões
},
});
| Cenário | samplingRate recomendado |
|---|---|
| Desenvolvimento | 1 (100%) |
| Staging | 1 (100%) |
| Produção — baixo tráfego (< 10k DAU) | 1 (100%) |
| Produção — médio tráfego (10k-100k DAU) | 0.5 (50%) |
| Produção — alto tráfego (> 100k DAU) | 0.1 - 0.25 |
O sampling é por sessão: quando uma sessão é amostrada, todos os eventos daquela sessão são capturados. Isso garante que você tem a visão completa de cada sessão amostrada.
CORS e segurança
ALLOW_ORIGINS no Collector FE
O Collector FE valida a origem das requests via CORS. Configure ALLOW_ORIGINS com os domínios da sua aplicação:
ALLOW_ORIGINS=https://app.meusite.com.br,https://*.meusite.com.br
Content Security Policy (CSP)
Se sua aplicação usa CSP, adicione o domínio do Collector FE:
Content-Security-Policy: connect-src 'self' https://collector-fe.meusite.com.br;
Dados sensíveis
O Faro captura URLs, console logs e payloads de erro. Garanta que:
- Tokens e credenciais nunca apareçam em URLs
- Console logs não contenham dados PII
- Mensagens de erro não exponham dados de negócio
Variáveis de ambiente por framework
React (Vite)
# .env
VITE_FARO_URL=https://collector-fe.meusite.com.br/collect/meu-tenant/eyJhbGci...
VITE_APP_VERSION=1.2.3
VITE_API_URL=https://api.meusite.com.br
React (Create React App)
# .env
REACT_APP_FARO_URL=https://collector-fe.meusite.com.br/collect/meu-tenant/eyJhbGci...
REACT_APP_VERSION=1.2.3
Next.js
# .env.local
NEXT_PUBLIC_FARO_URL=https://collector-fe.meusite.com.br/collect/meu-tenant/eyJhbGci...
NEXT_PUBLIC_APP_VERSION=1.2.3
NEXT_PUBLIC_APP_NAME=minha-app-next
NEXT_PUBLIC_API_URL=https://api.meusite.com.br
Angular
// src/environments/environment.prod.ts
export const environment = {
production: true,
faroUrl: 'https://collector-fe.meusite.com.br/collect/meu-tenant/eyJhbGci...',
appName: 'minha-app-angular',
appVersion: '1.2.3',
apiUrl: 'https://api.meusite.com.br',
};
Vue.js (Vite)
# .env
VITE_FARO_URL=https://collector-fe.meusite.com.br/collect/meu-tenant/eyJhbGci...
VITE_APP_VERSION=1.2.3
Boas práticas
Inicialização
- Inicialize o Faro antes de qualquer outro código — isso garante que erros durante a inicialização da app sejam capturados
- Separe a configuração em um arquivo dedicado (
src/faro.ts) e importe no entry point - Use variáveis de ambiente para a URL do Collector FE — nunca hardcode tokens no código
Eventos e logs
- Nomeie eventos com padrão consistente — use
snake_casee prefixos por domínio:checkout.completed,search.performed,auth.login_failed - Não envie dados sensíveis — CPF, senhas, tokens, números de cartão nunca devem aparecer em eventos, logs ou atributos
- Use domínios para agrupar eventos — o terceiro parâmetro de
pushEventagrupa eventos logicamente
Performance
- Habilite batching — reduz o número de requests HTTP para o Collector FE
- Habilite dedupe — evita envio de sinais duplicados (erros em loop, por exemplo)
- Ajuste o samplingRate em produção — para apps com alto tráfego, 10-25% é suficiente
Tracing
- Configure
propagateTraceHeaderCorsUrls— sem isso, as requests para APIs externas não terão trace context - Inclua apenas APIs próprias — nunca propague trace headers para APIs de terceiros (pode vazar informação)
Deploy
- Faça upload de source maps — sem eles, os stacktraces de erros ficam ilegíveis em produção
- Versionamento — sempre preencha
app.versioncom a versão real do deploy para correlacionar erros com releases
Troubleshooting
Nenhum dado aparece no Grafana
- Abra o DevTools do browser → aba Network
- Filtre por
collectpara encontrar as requests do Faro - Verifique:
* Status 200: dados estão sendo enviados com sucesso
* Status 0 / CORS error: o domínio não está em
ALLOW_ORIGINSdo Collector FE * Status 401: o JWT na URL é inválido * Nenhuma request: o Faro não foi inicializado (verifique o import no entry point)
Erros CORS no console
Access to fetch at 'https://collector-fe...' has been blocked by CORS policy
Solução: Adicione o domínio da sua aplicação em ALLOW_ORIGINS no Collector FE e reinicie o serviço.
Traces não propagam para o backend
- Verifique que
TracingInstrumentationestá nas instrumentations - Confirme que a URL da API está em
propagateTraceHeaderCorsUrls - O backend deve aceitar os headers
traceparentetracestatevia CORS
Web Vitals não aparecem
- Web Vitals são coletadas apenas em page loads reais (não em SPAs sem reload)
- Algumas métricas (LCP, CLS) só são reportadas quando o usuário sai da página ou ela vai para background
- Em desenvolvimento com Hot Module Replacement, os valores podem ser inconsistentes
Console logs não são capturados
Verifique que captureConsole: true está configurado:
...getWebInstrumentations({
captureConsole: true,
captureConsoleDisabledLevels: [], // sem essa linha, 'debug' e 'trace' são ignorados
}),
Dados duplicados
Habilite o dedupe:
initializeFaro({
dedupe: { enabled: true, interval: 1000 },
// ...
});
FAQ
O Faro SDK aumenta o bundle size da minha aplicação?
O @grafana/faro-web-sdk tem \~15 KB gzipped. O @grafana/faro-web-tracing adiciona mais \~45 KB gzipped (por incluir OpenTelemetry-JS). Use tree-shaking e lazy loading para minimizar o impacto.
Posso usar o Faro com micro-frontends / composable frontends?
Sim. Inicialize o Faro uma vez no shell/host application e compartilhe a instância com os micro-frontends. Use faro.api.setView() para marcar qual micro-frontend está ativo.
O Faro funciona com SSR (Server-Side Rendering)?
O Faro é client-side only. Em frameworks com SSR (Next.js, Nuxt), garanta que a inicialização ocorra apenas no browser (use typeof window !== 'undefined' ou diretivas 'use client').
Posso usar o Faro sem o TracingInstrumentation?
Sim. O tracing é opcional. Sem ele, você ainda captura Web Vitals, erros, console logs, eventos, medições e sessões.
O que acontece se o Collector FE estiver fora do ar?
O Faro descarta os dados silenciosamente — não causa erros visíveis para o usuário nem afeta a performance da aplicação.
Como faço para filtrar dados no Grafana por aplicação?
Use o label app no Loki:
{app="minha-app-react"} | json
Para filtrar por tipo de dado:
{app="minha-app-react", kind="exception"} | json
Qual a diferença entre pushEvent e pushLog?
pushEventé para interações de negócio (compras, buscas, cliques) — aparece como evento com nome e atributospushLogé para informações operacionais (debug, warnings, erros) — aparece como log com nível e contexto
Como funciona o sampling por sessão?
Quando samplingRate: 0.1, o Faro decide na criação da sessão se ela será amostrada (10% de chance). Se a sessão for amostrada, todos os dados daquela sessão são capturados. Sessões não amostradas não enviam nenhum dado.