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

```
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.elven.works/elven-platform/elven-observability/integracao-e-instrumentacao/javascript/instrumentacao-frontend-com-grafana-faro-web-sdk.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
