# 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

```bash
# 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)

```html
<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`

```typescript
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)

```typescript
// 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 }),
    }),
  ],
});
```

```typescript
// 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);
```

```tsx
// 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)

```typescript
// 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 }),
    }),
  ],
});
```

```typescript
// 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:

```tsx
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:

```tsx
<FaroErrorBoundary fallback={<ErrorFallback />}>
  <CheckoutForm />
</FaroErrorBoundary>

<FaroErrorBoundary fallback={<ErrorFallback />}>
  <ProductRecommendations />
</FaroErrorBoundary>
```

#### Component Profiling

Meça o tempo de renderização de componentes específicos:

```tsx
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:

```tsx
// 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;
}
```

```tsx
// 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**:

```typescript
// 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;
}
```

```tsx
// 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}</>;
}
```

```tsx
// 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):**

```tsx
// 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

```tsx
// 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:

```bash
npm install @grafana/faro-web-sdk @grafana/faro-web-tracing
```

```typescript
// 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;
  }
}
```

```typescript
// 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:**

```typescript
// 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);
  }
}
```

```typescript
// 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

```bash
npm install @grafana/faro-web-sdk @grafana/faro-web-tracing
```

```typescript
// 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;
}
```

```typescript
// 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

```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`

```typescript
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

```typescript
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

```typescript
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:

```typescript
// 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:

```typescript
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:

```typescript
// 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

```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

```typescript
// 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 (via `localStorage`)
* **Eventos de lifecycle:** `session_start`, `session_resume`, `session_extend`

#### Configuração

```typescript
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

```typescript
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](https://web.dev/vitals/):

| 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:

```typescript
// 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:

```typescript
// 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:

```typescript
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)

```bash
# .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)

```bash
# .env
REACT_APP_FARO_URL=https://collector-fe.meusite.com.br/collect/meu-tenant/eyJhbGci...
REACT_APP_VERSION=1.2.3
```

#### Next.js

```bash
# .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

```typescript
// 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)

```bash
# .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

1. **Inicialize o Faro antes de qualquer outro código** — isso garante que erros durante a inicialização da app sejam capturados
2. **Separe a configuração em um arquivo dedicado** (`src/faro.ts`) e importe no entry point
3. **Use variáveis de ambiente** para a URL do Collector FE — nunca hardcode tokens no código

#### Eventos e logs

4. **Nomeie eventos com padrão consistente** — use `snake_case` e prefixos por domínio: `checkout.completed`, `search.performed`, `auth.login_failed`
5. **Não envie dados sensíveis** — CPF, senhas, tokens, números de cartão nunca devem aparecer em eventos, logs ou atributos
6. **Use domínios para agrupar eventos** — o terceiro parâmetro de `pushEvent` agrupa eventos logicamente

#### Performance

7. **Habilite batching** — reduz o número de requests HTTP para o Collector FE
8. **Habilite dedupe** — evita envio de sinais duplicados (erros em loop, por exemplo)
9. **Ajuste o samplingRate em produção** — para apps com alto tráfego, 10-25% é suficiente

#### Tracing

10. **Configure `propagateTraceHeaderCorsUrls`** — sem isso, as requests para APIs externas não terão trace context
11. **Inclua apenas APIs próprias** — nunca propague trace headers para APIs de terceiros (pode vazar informação)

#### Deploy

12. **Faça upload de source maps** — sem eles, os stacktraces de erros ficam ilegíveis em produção
13. **Versionamento** — sempre preencha `app.version` com a versão real do deploy para correlacionar erros com releases

***

### Troubleshooting

#### Nenhum dado aparece no Grafana

1. Abra o DevTools do browser → aba Network
2. Filtre por `collect` para encontrar as requests do Faro
3. Verifique:
   * **Status 200**: dados estão sendo enviados com sucesso
   * **Status 0 / CORS error**: o domínio não está em `ALLOW_ORIGINS` do 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

1. Verifique que `TracingInstrumentation` está nas instrumentations
2. Confirme que a URL da API está em `propagateTraceHeaderCorsUrls`
3. O backend deve aceitar os headers `traceparent` e `tracestate` via 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:

```typescript
...getWebInstrumentations({
  captureConsole: true,
  captureConsoleDisabledLevels: [],  // sem essa linha, 'debug' e 'trace' são ignorados
}),
```

#### Dados duplicados

Habilite o dedupe:

```typescript
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 atributos
* `pushLog` é 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.

<br>

```
```
