# Collector FE — Instrumentação Frontend com Grafana Faro

Ele recebe payloads do Faro Web SDK pelo endpoint `POST /collect/:tenant/:token` e faz fan-out para os três sinais da stack LGTM:

* **Loki**: logs estruturados, eventos, medições e exceções.
* **Mimir**: métricas RUM via Prometheus `remote_write`.
* **Tempo**: traces via OTLP/HTTP protobuf a partir de `traces.resourceSpans`.

> O Collector FE é implantado na infraestrutura do cliente ou no cluster Elven dedicado ao cliente. A Elven fornece os endpoints e tokens de escrita. Para Elven Cloud, normalmente o mesmo tenant/token usado em Loki também é reutilizado em Mimir e Tempo.

***

### Índice

* Visão geral
* Arquitetura
* Contrato de payload
* Compatibilidade com clientes antigos
* Autenticação e multi-tenancy
* Variáveis de ambiente
* Instalação Linux via script
* Deploy Docker
* Deploy Kubernetes
* Configuração do Faro no frontend
* Métricas geradas
* Logs estruturados
* Traces
* Migração de clientes antigos
* Health check e validação
* Troubleshooting
* FAQ

***

### Visão geral

O Collector FE é um serviço HTTP em Go especializado em payloads do **Grafana Faro Web SDK 2.3.1**. Ele não substitui o OpenTelemetry Collector de backend; ele existe para receber telemetria de navegador de forma segura, validar o tenant/token e converter os sinais para a stack LGTM.

Fluxo de request:

1. O browser envia `POST /collect/<tenant>/<jwt-token>`.
2. O collector valida o JWT HS256 usando `SECRET_KEY`.
3. O body é decodificado como payload Faro 2.3.1.
4. O payload é normalizado em logs, observações de métricas e traces.
5. Os sinks habilitados recebem o lote em filas internas.
6. O request retorna `200` quando todos os sinks habilitados aceitarem o lote na fila.
7. Se uma fila obrigatória estiver cheia ou indisponível para enqueue, o request retorna `503`.
8. Falhas depois do enqueue viram logs/métricas internas do collector e não mudam a resposta já enviada ao browser.

***

### Arquitetura

```
Browser / SPA
  Grafana Faro Web SDK 2.3.1
  @grafana/faro-web-tracing 2.3.1 opcional para traces
        |
        | POST /collect/:tenant/:jwt
        v
Collector FE
  decode -> normalize -> dispatch
        |
        +--> Loki  /loki/api/v1/push
        |
        +--> Mimir /api/v1/push          Prometheus remote_write
        |
        +--> Tempo /v1/traces            OTLP/HTTP protobuf
```

Separação de responsabilidades:

| Camada       | Responsabilidade                                                                                          |
| ------------ | --------------------------------------------------------------------------------------------------------- |
| Frontend     | Inicializar Faro, definir app/environment/release, habilitar tracing quando necessário e enviar payloads. |
| Collector FE | Validar JWT, preservar contexto rico, controlar cardinalidade, gerar métricas RUM e despachar para sinks. |
| Loki         | Armazenar sinais ricos e investigáveis como logs estruturados.                                            |
| Mimir        | Armazenar séries de métricas agregadas para dashboards e alertas.                                         |
| Tempo        | Armazenar spans enviados pelo Faro tracing.                                                               |

***

### Contrato de payload

O contrato primário é o `TransportBody` do Faro `2.3.1`.

Campos de topo suportados:

| Campo          | Uso                                                                                 |
| -------------- | ----------------------------------------------------------------------------------- |
| `meta`         | Metadados de app, browser, usuário, sessão, página e view.                          |
| `logs`         | Logs capturados ou enviados manualmente pelo Faro.                                  |
| `events`       | Eventos de navegação, recurso, user action, tracing sidecar e eventos customizados. |
| `measurements` | Web Vitals e medições customizadas.                                                 |
| `exceptions`   | Exceções JavaScript com stacktrace, contexto e fingerprint.                         |
| `traces`       | Objeto OTLP contendo `resourceSpans`.                                               |

Metadados relevantes:

| Campo          | Exemplos                                                                                                |
| -------------- | ------------------------------------------------------------------------------------------------------- |
| `meta.app`     | `name`, `namespace`, `release`, `version`, `environment`, `bundleId`                                    |
| `meta.user`    | `id`, `email`, `username`, `fullName`, `roles`, `hash`, `attributes`                                    |
| `meta.session` | `id`, `attributes`, `overrides`                                                                         |
| `meta.page`    | `id`, `url`, `attributes`                                                                               |
| `meta.browser` | `name`, `version`, `os`, `mobile`, `userAgent`, `language`, `brands`, `viewportWidth`, `viewportHeight` |

Sinais nativos tratados pelo collector:

| Sinal Faro                                    | Uso no collector                                           |
| --------------------------------------------- | ---------------------------------------------------------- |
| `measurements.type="web-vitals"`              | Gera métricas para `CLS`, `FCP`, `INP`, `LCP` e `TTFB`.    |
| `events.name="faro.performance.navigation"`   | Gera métricas de navegação e fases de navegação.           |
| `events.name="faro.performance.resource"`     | Gera métricas de recursos, duração e bytes transferidos.   |
| `events.name="faro.user.action"`              | Fonte de verdade para métricas de user action.             |
| `events.name="faro.tracing.fetch"`            | Gera métricas HTTP client para fetch.                      |
| `events.name="faro.tracing.xml-http-request"` | Gera métricas HTTP client para XHR.                        |
| `events.name="faro.navigation"`               | Opcional; só gera métricas se o evento existir no payload. |

O collector não deriva métricas de traces brutos para evitar dupla contagem. Para HTTP client, use os eventos sidecar `faro.tracing.*`.

***

### Compatibilidade com clientes antigos

O collector mantém compatibilidade com o payload JSON legado já aceito pela versão anterior quando ele representa logs, eventos, medições ou exceções do Faro. Esse payload é normalizado internamente para o modelo atual.

Limites importantes:

* Payload Faro antigo em JSON continua aceito quando contém os campos estruturados que o collector já suportava.
* Clientes que enviavam **raw logfmt puro** não devem ser migrados trocando apenas a URL. Eles precisam passar a enviar payload Faro JSON via SDK.
* `FID` não é premissa da instrumentação atual. A métrica moderna de interação é `INP`.
* `traces` só chegam quando o frontend instala `@grafana/faro-web-tracing` e registra `TracingInstrumentation`.
* Custom measurements sem regra explícita geram contagem em `faro_frontend_custom_measurements_total`; séries semânticas detalhadas exigem `METRICS_CUSTOM_MEASUREMENT_RULES`.

***

### Autenticação e multi-tenancy

#### Endpoint

```
POST /collect/:tenant/:token
```

| Parte    | Descrição                                                                           |
| -------- | ----------------------------------------------------------------------------------- |
| `tenant` | Identifica cliente/produto. Por padrão vira `X-Scope-OrgID` nos sinks multi-tenant. |
| `token`  | JWT HS256 assinado com a `SECRET_KEY` configurada no collector.                     |

#### JWT

Claims obrigatórias:

| Claim  | Regra                          |
| ------ | ------------------------------ |
| `role` | Deve ser `admin` ou `user`.    |
| `iss`  | Deve ser igual a `JWT_ISSUER`. |

Claim opcional:

| Claim | Regra                                         |
| ----- | --------------------------------------------- |
| `exp` | Só é validada quando `JWT_VALIDATE_EXP=true`. |

Exemplo:

```json
{
  "role": "user",
  "iss": "trusted-issuer",
  "exp": 1770000000,
  "sub": "minha-app-frontend"
}
```

> O JWT fica visível no browser porque faz parte da URL do Faro. Use `role: user`, limite CORS por domínio e, em produção, prefira `JWT_VALIDATE_EXP=true` com rotação planejada.

***

### Variáveis de ambiente

#### Essenciais

| Variável           | Obrigatória | Default          | Descrição                                                                 |
| ------------------ | ----------- | ---------------- | ------------------------------------------------------------------------- |
| `SECRET_KEY`       | Sim         | -                | Chave HS256 para validar JWTs. Mínimo de 64 caracteres.                   |
| `LOKI_URL`         | Sim         | -                | URL base do Loki. O collector adiciona `/loki/api/v1/push`.               |
| `LOKI_API_TOKEN`   | Sim         | -                | Token enviado como `Authorization: Bearer ...` para Loki.                 |
| `ALLOW_ORIGINS`    | Sim         | -                | Origens CORS separadas por vírgula. Aceita `*` e `https://*.example.com`. |
| `PORT`             | Não         | `3000`           | Porta HTTP do collector.                                                  |
| `JWT_ISSUER`       | Não         | `trusted-issuer` | Valor esperado na claim `iss`.                                            |
| `JWT_VALIDATE_EXP` | Não         | `false`          | Rejeita JWT expirado quando `true`.                                       |

`LOKI_URL` deve ser base, por exemplo:

```bash
LOKI_URL="https://loki.elvenobservability.com"
```

Não use:

```bash
LOKI_URL="https://loki.elvenobservability.com/loki/api/v1/push"
```

#### Loki

| Variável               | Default | Descrição                                                         |
| ---------------------- | ------- | ----------------------------------------------------------------- |
| `LOKI_TIMEOUT`         | `15s`   | Timeout por chamada ao Loki.                                      |
| `LOKI_QUEUE_SIZE`      | `256`   | Tamanho da fila interna de streams.                               |
| `LOKI_ENQUEUE_TIMEOUT` | `2s`    | Tempo máximo esperando espaço na fila.                            |
| `LOKI_RETRY_ATTEMPTS`  | `3`     | Tentativas de exportação por lote.                                |
| `LOKI_RETRY_BACKOFF`   | `500ms` | Intervalo entre tentativas.                                       |
| `LOKI_MAX_EVENT_AGE`   | `55m`   | Eventos mais antigos têm timestamp ajustado para evitar rejeição. |
| `LOKI_MAX_FUTURE_SKEW` | `10m`   | Eventos muito no futuro têm timestamp ajustado.                   |

#### Mimir remote\_write

Métricas são opcionais e ficam desligadas por padrão.

| Variável                              | Default           | Descrição                                                                   |
| ------------------------------------- | ----------------- | --------------------------------------------------------------------------- |
| `METRICS_ENABLED`                     | `false`           | Habilita exportação Prometheus `remote_write`.                              |
| `METRICS_REMOTE_WRITE_URL`            | -                 | Endpoint completo do remote\_write. Obrigatório quando habilitado.          |
| `METRICS_REMOTE_WRITE_TIMEOUT`        | `10s`             | Timeout do cliente remote\_write.                                           |
| `METRICS_REMOTE_WRITE_FLUSH_INTERVAL` | `10s`             | Intervalo de flush das séries agregadas.                                    |
| `METRICS_QUEUE_SIZE`                  | `256`             | Tamanho da fila de observações.                                             |
| `METRICS_ENQUEUE_TIMEOUT`             | `2s`              | Tempo máximo esperando espaço na fila.                                      |
| `METRICS_TENANT_MODE`                 | `request`         | `request`, `fixed` ou `none`.                                               |
| `METRICS_TENANT_ID`                   | -                 | Tenant usado quando `METRICS_TENANT_MODE=fixed`.                            |
| `METRICS_LABEL_ALLOWLIST`             | allowlist interna | Substitui a allowlist padrão de labels.                                     |
| `METRICS_CUSTOM_MEASUREMENT_RULES`    | -                 | JSON no formato `{"tipo":["campo"]}` para medições customizadas detalhadas. |
| `METRICS_RETRY_ATTEMPTS`              | `3`               | Tentativas de envio remote\_write.                                          |
| `METRICS_RETRY_BACKOFF`               | `500ms`           | Intervalo entre tentativas.                                                 |

Auth do remote\_write:

| Variável                                                                           | Descrição                                                                      |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `METRICS_REMOTE_WRITE_AUTH_BEARER_TOKEN`                                           | Envia `Authorization: Bearer ...`.                                             |
| `METRICS_REMOTE_WRITE_AUTH_USERNAME` / `METRICS_REMOTE_WRITE_AUTH_PASSWORD`        | Envia Basic Auth.                                                              |
| `METRICS_REMOTE_WRITE_AUTH_HEADER_NAME` / `METRICS_REMOTE_WRITE_AUTH_HEADER_VALUE` | Envia header customizado. As duas variáveis precisam estar preenchidas juntas. |

#### Tempo OTLP/HTTP

Traces são opcionais e ficam desligados por padrão.

| Variável                       | Default                             | Descrição                                                   |
| ------------------------------ | ----------------------------------- | ----------------------------------------------------------- |
| `TRACES_ENABLED`               | `false`                             | Habilita exportação de `traces.resourceSpans`.              |
| `TRACES_OTLP_HTTP_URL`         | -                                   | Endpoint completo OTLP/HTTP. Obrigatório quando habilitado. |
| `TRACES_OTLP_HTTP_TIMEOUT`     | `10s`                               | Timeout do cliente OTLP/HTTP.                               |
| `TRACES_OTLP_HTTP_COMPRESSION` | `none` no binário, `gzip` no script | `none` ou `gzip`.                                           |
| `TRACES_FLUSH_INTERVAL`        | `2s`                                | Intervalo de flush dos batches.                             |
| `TRACES_QUEUE_SIZE`            | `256`                               | Tamanho da fila de traces.                                  |
| `TRACES_ENQUEUE_TIMEOUT`       | `2s`                                | Tempo máximo esperando espaço na fila.                      |
| `TRACES_BATCH_SIZE`            | `20`                                | Quantidade de resource spans para flush antecipado.         |
| `TRACES_TENANT_MODE`           | `request`                           | `request`, `fixed` ou `none`.                               |
| `TRACES_TENANT_ID`             | -                                   | Tenant usado quando `TRACES_TENANT_MODE=fixed`.             |
| `TRACES_TENANT_HEADER`         | `X-Scope-OrgID`                     | Header usado para propagar tenant.                          |
| `TRACES_RETRY_ATTEMPTS`        | `3`                                 | Tentativas de envio OTLP/HTTP.                              |
| `TRACES_RETRY_BACKOFF`         | `500ms`                             | Intervalo entre tentativas.                                 |

Auth do OTLP/HTTP:

| Variável                                                         | Descrição                                                                      |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `TRACES_OTLP_AUTH_BEARER_TOKEN`                                  | Envia `Authorization: Bearer ...`.                                             |
| `TRACES_OTLP_AUTH_USERNAME` / `TRACES_OTLP_AUTH_PASSWORD`        | Envia Basic Auth.                                                              |
| `TRACES_OTLP_AUTH_HEADER_NAME` / `TRACES_OTLP_AUTH_HEADER_VALUE` | Envia header customizado. As duas variáveis precisam estar preenchidas juntas. |

***

### Instalação Linux via script

O caminho recomendado para VM, EC2, bare metal e servidores Linux com systemd é o script:

```bash
curl -sSL https://raw.githubusercontent.com/elven-observability/scripts/main/linux/collector-fe/install.sh | sudo bash
```

O instalador:

* Detecta a distribuição Linux.
* Baixa o binário de release.
* Valida as variáveis obrigatórias.
* Remove `/loki/api/v1/push` de `LOKI_URL` se alguém informar a URL antiga.
* Permite habilitar Mimir e Tempo no modo interativo.
* Cria `/etc/collector-fe-instrumentation/env` com permissão `600`.
* Cria e inicia o serviço `collector-fe-instrumentation`.
* Opcionalmente instala Caddy para HTTPS automático.

#### Script com envs: Loki somente

```bash
sudo SECRET_KEY="sua-chave-com-pelo-menos-64-caracteres-aqui-coloque-algo-bem-longo" \
     LOKI_URL="https://loki.elvenobservability.com" \
     LOKI_API_TOKEN="token-fornecido-pela-elven" \
     ALLOW_ORIGINS="https://app.meusite.com.br,https://*.meusite.com.br" \
     bash <(curl -sSL https://raw.githubusercontent.com/elven-observability/scripts/main/linux/collector-fe/install.sh)
```

Para produção, prefira pinagem explícita da release do binário:

```bash
COLLECTOR_VERSION="<release-tag>"
```

#### Script com envs: Loki + Mimir + Tempo

```bash
sudo SECRET_KEY="sua-chave-com-pelo-menos-64-caracteres-aqui-coloque-algo-bem-longo" \
     LOKI_URL="https://loki.elvenobservability.com" \
     LOKI_API_TOKEN="token-fornecido-pela-elven" \
     ALLOW_ORIGINS="https://app.meusite.com.br,https://*.meusite.com.br" \
     METRICS_ENABLED="true" \
     METRICS_REMOTE_WRITE_URL="https://mimir.elvenobservability.com/api/v1/push" \
     TRACES_ENABLED="true" \
     TRACES_OTLP_HTTP_URL="https://tempo.elvenobservability.com/http/v1/traces" \
     bash <(curl -sSL https://raw.githubusercontent.com/elven-observability/scripts/main/linux/collector-fe/install.sh)
```

Por padrão, quando Mimir/Tempo estão habilitados no script, `LOKI_API_TOKEN` é reaproveitado como bearer token desses sinks. Para desabilitar esse comportamento:

```bash
REUSE_LOKI_TOKEN_FOR_SINK_AUTH=false
```

Depois configure uma destas opções:

* `METRICS_REMOTE_WRITE_AUTH_BEARER_TOKEN`
* `METRICS_REMOTE_WRITE_AUTH_USERNAME` / `METRICS_REMOTE_WRITE_AUTH_PASSWORD`
* `METRICS_REMOTE_WRITE_AUTH_HEADER_NAME` / `METRICS_REMOTE_WRITE_AUTH_HEADER_VALUE`
* `TRACES_OTLP_AUTH_BEARER_TOKEN`
* `TRACES_OTLP_AUTH_USERNAME` / `TRACES_OTLP_AUTH_PASSWORD`
* `TRACES_OTLP_AUTH_HEADER_NAME` / `TRACES_OTLP_AUTH_HEADER_VALUE`

#### Instalação com Caddy

```bash
sudo SECRET_KEY="sua-chave-com-pelo-menos-64-caracteres-aqui-coloque-algo-bem-longo" \
     LOKI_URL="https://loki.elvenobservability.com" \
     LOKI_API_TOKEN="token-fornecido-pela-elven" \
     ALLOW_ORIGINS="https://app.meusite.com.br" \
     INSTALL_CADDY=true \
     CADDY_DOMAIN="collector.meusite.com.br" \
     bash <(curl -sSL https://raw.githubusercontent.com/elven-observability/scripts/main/linux/collector-fe/install.sh)
```

O DNS de `CADDY_DOMAIN` precisa apontar para a VM antes da instalação.

#### Arquivos instalados

| Item    | Caminho                                                          |
| ------- | ---------------------------------------------------------------- |
| Binário | `/opt/collector-fe-instrumentation/collector-fe-instrumentation` |
| Env     | `/etc/collector-fe-instrumentation/env`                          |
| Serviço | `/etc/systemd/system/collector-fe-instrumentation.service`       |

***

### Deploy Docker

#### Loki somente

Em produção, use uma tag explícita no lugar de `<release-tag>`.

```bash
docker run -d \
  --name collector-fe \
  --restart unless-stopped \
  -p 3000:3000 \
  -e SECRET_KEY="sua-chave-com-pelo-menos-64-caracteres-aqui-coloque-algo-bem-longo" \
  -e LOKI_URL="https://loki.elvenobservability.com" \
  -e LOKI_API_TOKEN="token-fornecido-pela-elven" \
  -e ALLOW_ORIGINS="https://app.meusite.com.br,https://*.meusite.com.br" \
  -e JWT_ISSUER="trusted-issuer" \
  -e JWT_VALIDATE_EXP="false" \
  elvenobservability/collector-fe-instrumentation:<release-tag>
```

#### Loki + Mimir + Tempo

```bash
docker run -d \
  --name collector-fe \
  --restart unless-stopped \
  -p 3000:3000 \
  -e SECRET_KEY="sua-chave-com-pelo-menos-64-caracteres-aqui-coloque-algo-bem-longo" \
  -e LOKI_URL="https://loki.elvenobservability.com" \
  -e LOKI_API_TOKEN="token-fornecido-pela-elven" \
  -e ALLOW_ORIGINS="https://app.meusite.com.br,https://*.meusite.com.br" \
  -e METRICS_ENABLED="true" \
  -e METRICS_REMOTE_WRITE_URL="https://mimir.elvenobservability.com/api/v1/push" \
  -e METRICS_REMOTE_WRITE_AUTH_BEARER_TOKEN="token-fornecido-pela-elven" \
  -e TRACES_ENABLED="true" \
  -e TRACES_OTLP_HTTP_URL="https://tempo.elvenobservability.com/http/v1/traces" \
  -e TRACES_OTLP_HTTP_COMPRESSION="gzip" \
  -e TRACES_OTLP_AUTH_BEARER_TOKEN="token-fornecido-pela-elven" \
  elvenobservability/collector-fe-instrumentation:<release-tag>
```

***

### Deploy Kubernetes

Exemplo mínimo para clusters Elven ou clusters do cliente.

#### Secret

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: collector-fe-env-secret
  namespace: monitoring
type: Opaque
stringData:
  SECRET_KEY: "sua-chave-com-pelo-menos-64-caracteres-aqui-coloque-algo-bem-longo"
  LOKI_URL: "http://loki-gateway.loki.svc.cluster.local"
  LOKI_API_TOKEN: "token-fornecido-pela-elven"
  ALLOW_ORIGINS: "https://app.meusite.com.br,https://*.meusite.com.br"
  JWT_ISSUER: "trusted-issuer"
  JWT_VALIDATE_EXP: "false"

  LOKI_QUEUE_SIZE: "2048"
  LOKI_ENQUEUE_TIMEOUT: "2s"
  LOKI_RETRY_ATTEMPTS: "3"
  LOKI_RETRY_BACKOFF: "500ms"
  LOKI_MAX_EVENT_AGE: "55m"
  LOKI_MAX_FUTURE_SKEW: "10m"

  METRICS_ENABLED: "true"
  METRICS_REMOTE_WRITE_URL: "http://mimir-gateway.mimir.svc.cluster.local:8080/api/v1/push"
  METRICS_REMOTE_WRITE_FLUSH_INTERVAL: "10s"
  METRICS_QUEUE_SIZE: "2048"
  METRICS_ENQUEUE_TIMEOUT: "2s"
  METRICS_TENANT_MODE: "request"
  METRICS_RETRY_ATTEMPTS: "3"
  METRICS_RETRY_BACKOFF: "500ms"

  TRACES_ENABLED: "true"
  TRACES_OTLP_HTTP_URL: "http://tempo-distributor.tempo.svc.cluster.local:4318/v1/traces"
  TRACES_OTLP_HTTP_COMPRESSION: "gzip"
  TRACES_QUEUE_SIZE: "2048"
  TRACES_ENQUEUE_TIMEOUT: "2s"
  TRACES_BATCH_SIZE: "20"
  TRACES_TENANT_MODE: "request"
  TRACES_TENANT_HEADER: "X-Scope-OrgID"
  TRACES_RETRY_ATTEMPTS: "3"
  TRACES_RETRY_BACKOFF: "500ms"
```

> Em cluster interno, prefira endpoints internos de Loki/Mimir/Tempo para evitar autenticação de borda entre componentes do mesmo cluster. Em endpoint público, configure auth bearer, basic ou header customizado conforme fornecido pela Elven.

#### values.yaml

```yaml
replicaCount: 2

image:
  registry: docker.io
  repository: elvenobservability/collector-fe-instrumentation
  pullPolicy: IfNotPresent
  tag: "<release-tag>"

envFrom:
  - secretRef:
      name: collector-fe-env-secret

extraEnvs:
  - name: METRICS_REMOTE_WRITE_AUTH_BEARER_TOKEN
    valueFrom:
      secretKeyRef:
        name: collector-fe-env-secret
        key: LOKI_API_TOKEN
  - name: TRACES_OTLP_AUTH_BEARER_TOKEN
    valueFrom:
      secretKeyRef:
        name: collector-fe-env-secret
        key: LOKI_API_TOKEN

containerPorts:
  - name: http
    containerPort: 3000
    protocol: TCP

service:
  type: ClusterIP
  servicePorts:
    - name: http
      port: 3000
      targetPort: 3000

resources:
  requests:
    cpu: 50m
    memory: 64Mi
  limits:
    cpu: 500m
    memory: 256Mi

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
```

Validação:

```bash
kubectl -n monitoring rollout status deploy/collector-fe --timeout=180s
kubectl -n monitoring logs deploy/collector-fe --tail=100
kubectl -n monitoring port-forward svc/collector-fe-service 3000:3000
curl http://localhost:3000/health
```

***

### Configuração do Faro no frontend

Instale as versões da família Faro 2.3.1:

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

Inicialização base:

```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-frontend',
    namespace: 'web',
    version: import.meta.env.VITE_APP_VERSION || '0.0.0',
    release: import.meta.env.VITE_APP_RELEASE || 'local',
    environment: import.meta.env.MODE,
  },
  sessionTracking: {
    enabled: true,
    persistent: true,
    samplingRate: 1,
  },
  instrumentations: [
    ...getWebInstrumentations({
      captureConsole: true,
      captureConsoleDisabledLevels: [],
    }),
    new TracingInstrumentation({
      instrumentationOptions: {
        propagateTraceHeaderCorsUrls: [
          /https:\/\/api\.meusite\.com\.br\/.*/,
        ],
      },
    }),
  ],
});
```

Env de frontend:

```bash
VITE_FARO_URL="https://collector.meusite.com.br/collect/meu-tenant/eyJhbGciOiJIUzI1NiIs..."
VITE_APP_VERSION="1.4.2"
VITE_APP_RELEASE="2026.05.18-1"
```

Sem `@grafana/faro-web-tracing`, o collector continua recebendo logs, events, measurements e exceptions, mas não terá `traces.resourceSpans` para enviar ao Tempo.

***

### Métricas geradas

Quando `METRICS_ENABLED=true`, o collector exporta histogramas clássicos Prometheus e contadores via `remote_write`.

Famílias principais:

| Métrica                                       | Uso                                               |
| --------------------------------------------- | ------------------------------------------------- |
| `faro_collector_requests_total`               | Requests recebidos pelo collector.                |
| `faro_collector_request_duration_seconds`     | Latência do endpoint `/collect`.                  |
| `faro_collector_payload_bytes_total`          | Volume recebido.                                  |
| `faro_collector_sink_queue_length`            | Tamanho das filas por sink.                       |
| `faro_collector_sink_enqueue_failures_total`  | Falhas para aceitar lote em fila.                 |
| `faro_collector_sink_export_failures_total`   | Falhas de export pós-enqueue.                     |
| `faro_collector_sink_export_duration_seconds` | Duração dos exports por sink.                     |
| `faro_frontend_signals_total`                 | Total de sinais normalizados.                     |
| `faro_frontend_logs_total`                    | Logs por app/level.                               |
| `faro_frontend_exceptions_total`              | Exceções por app/browser.                         |
| `faro_frontend_web_vital_seconds`             | `fcp`, `inp`, `lcp`, `ttfb`.                      |
| `faro_frontend_web_vital_ratio`               | `cls`.                                            |
| `faro_frontend_web_vital_phase_seconds`       | Campos numéricos de attribution.                  |
| `faro_frontend_navigation_duration_seconds`   | Duração de navegação.                             |
| `faro_frontend_navigation_phase_seconds`      | Fases de navegação.                               |
| `faro_frontend_resource_duration_seconds`     | Duração de recursos.                              |
| `faro_frontend_resource_transfer_bytes`       | Bytes transferidos por recurso.                   |
| `faro_frontend_user_actions_total`            | Ações de usuário.                                 |
| `faro_frontend_user_action_duration_seconds`  | Duração de ações.                                 |
| `faro_frontend_http_client_requests_total`    | Requests fetch/XHR derivados de `faro.tracing.*`. |
| `faro_frontend_http_client_duration_seconds`  | Latência fetch/XHR.                               |
| `faro_frontend_custom_measurements_total`     | Contagem de custom measurements.                  |
| `faro_frontend_custom_measurement_value`      | Valores detalhados apenas por regra explícita.    |

Labels de baixa cardinalidade promovidas por padrão:

```
tenant, app, namespace, release, environment, browser_name, browser_os, view_name,
vital, rating, navigation_type, initiator_type, cache_hit_status, render_blocking_status,
action_name, importance, trigger, transport, method, status_class, host
```

Nunca promova como label:

```
session.id, page.url, trace_id, span_id, user.*, fingerprint, element,
interaction_target, URL completa, IDs arbitrários, atributos livres
```

***

### Logs estruturados

Loki mantém os dados ricos para investigação. Os campos úteis incluem:

* `trace_id`
* `span_id`
* `action_name`
* `session_id`
* `user_id`
* `page_url`
* `event_name`
* `measurement_type`
* atributos originais relevantes

Exemplo de consulta:

```logql
{app="minha-app-frontend", environment="production"}
```

Para investigar erro:

```logql
{app="minha-app-frontend", kind="exception"} | json
```

***

### Traces

Para traces funcionarem, duas coisas precisam estar verdadeiras:

* Collector com `TRACES_ENABLED=true` e `TRACES_OTLP_HTTP_URL` configurado.
* Frontend usando `@grafana/faro-web-tracing@2.3.1` com `TracingInstrumentation`.

O collector exporta o subobjeto `traces` como `ExportTraceServiceRequest` OTLP/HTTP protobuf. O endpoint deve ser completo para evitar ambiguidade:

```bash
TRACES_OTLP_HTTP_URL="https://tempo.example.com/v1/traces"
```

Em alguns ingresses da Elven, o endpoint público HTTP usa prefixo:

```bash
TRACES_OTLP_HTTP_URL="https://tempo.elvenobservability.com/http/v1/traces"
```

Em Kubernetes interno:

```bash
TRACES_OTLP_HTTP_URL="http://tempo-distributor.tempo.svc.cluster.local:4318/v1/traces"
```

***

### Migração de clientes antigos

Use este fluxo para clientes que estavam com versão antiga ou integração "só logfmt".

1. **Subir o collector novo primeiro.** Habilite Loki e, se possível, Mimir/Tempo já na primeira janela.
2. **Manter o caminho antigo em paralelo.** Não remova o fluxo antigo até validar que o frontend novo está mandando Faro JSON.
3. **Atualizar o frontend.** Instale `@grafana/faro-web-sdk@2.3.1` e, para traces, `@grafana/faro-web-tracing@2.3.1`.
4. **Configurar `VITE_FARO_URL` ou equivalente.** A URL deve apontar para `/collect/<tenant>/<jwt>`.
5. **Canariar por uma aplicação ou ambiente.** Comece por staging ou uma porcentagem baixa de tráfego.
6. **Validar os três sinais.** Confirme logs no Loki, métricas no Mimir e traces no Tempo.
7. **Comparar com o dashboard antigo por 24h.** Verifique se erros, sessões e tráfego batem.
8. **Cortar o caminho antigo.** Só depois de confirmar que não há perda de dados relevantes.

Critérios de aceite:

* `POST /collect/:tenant/:token` retorna `200`.
* Loki mostra logs/events/exceptions com `app` e `environment` corretos.
* Mimir recebe `faro_frontend_signals_total`.
* Web Vitals incluem `INP` e não dependem de `FID`.
* Tempo mostra traces para a app quando tracing está habilitado no frontend.
* Não há erro de export nos logs do collector.

***

### Health check e validação

Endpoint:

```
GET /health
```

Resposta esperada:

```json
{"status":"ok"}
```

Comandos Linux:

```bash
systemctl status collector-fe-instrumentation
journalctl -u collector-fe-instrumentation -f
curl http://localhost:3000/health
```

Comandos Kubernetes:

```bash
kubectl -n monitoring get pods -l app.kubernetes.io/name=collector-fe
kubectl -n monitoring logs deploy/collector-fe --tail=100
kubectl -n monitoring port-forward svc/collector-fe-service 3000:3000
curl http://localhost:3000/health
```

Consultas úteis:

```promql
sum(rate(faro_collector_requests_total[5m]))
```

```promql
sum(rate(faro_frontend_signals_total[5m])) by (app, environment)
```

```logql
{app="minha-app-frontend"} | json
```

***

### Troubleshooting

#### Serviço não inicia

Verifique:

```bash
journalctl -u collector-fe-instrumentation -n 80 --no-pager
```

Erros comuns:

| Erro                                             | Correção                                      |
| ------------------------------------------------ | --------------------------------------------- |
| `missing required env: SECRET_KEY`               | Defina `SECRET_KEY`.                          |
| `SECRET_KEY must be at least 64 characters`      | Use uma chave maior.                          |
| `missing required env: LOKI_URL`                 | Configure a URL base do Loki.                 |
| `missing required env: LOKI_API_TOKEN`           | Configure o token.                            |
| `missing required env: ALLOW_ORIGINS`            | Configure CORS.                               |
| `missing required env: METRICS_REMOTE_WRITE_URL` | Defina a URL ou desabilite `METRICS_ENABLED`. |
| `missing required env: TRACES_OTLP_HTTP_URL`     | Defina a URL ou desabilite `TRACES_ENABLED`.  |

#### Faro retorna 401

Verifique:

* O JWT foi assinado com a mesma `SECRET_KEY`.
* O algoritmo é `HS256`.
* `role` é `admin` ou `user`.
* `iss` é igual a `JWT_ISSUER`.
* Se `JWT_VALIDATE_EXP=true`, o token não expirou.

#### Faro retorna 503

`503` indica que pelo menos um sink habilitado não conseguiu aceitar o lote na fila dentro de `*_ENQUEUE_TIMEOUT`.

Correções:

* Aumente `LOKI_QUEUE_SIZE`, `METRICS_QUEUE_SIZE` ou `TRACES_QUEUE_SIZE`.
* Verifique se o destino está respondendo.
* Aumente réplicas do collector.
* Verifique CPU/memória do pod ou da VM.

#### Logs chegam, mas métricas não

Verifique:

* `METRICS_ENABLED=true`.
* `METRICS_REMOTE_WRITE_URL` aponta para endpoint completo `/api/v1/push`.
* Auth do Mimir está correta.
* Não há erro em `faro_collector_sink_export_failures_total{sink="metrics"}`.
* O payload contém eventos/measurements que geram métricas.

#### Logs e métricas chegam, mas traces não

Verifique:

* `TRACES_ENABLED=true`.
* `TRACES_OTLP_HTTP_URL` aponta para endpoint completo OTLP/HTTP.
* Frontend instalou `@grafana/faro-web-tracing`.
* `TracingInstrumentation` foi registrado.
* O payload tem `traces.resourceSpans`.
* Não há erro em `faro_collector_sink_export_failures_total{sink="traces"}`.

#### URL do Loki com path antigo

Use somente a base:

```bash
LOKI_URL="https://loki.elvenobservability.com"
```

O collector adiciona `/loki/api/v1/push`. Informar o path completo pode gerar URL duplicada.

***

### FAQ

**O Collector FE envia traces também?**

Sim, quando `TRACES_ENABLED=true` e o payload contém `traces.resourceSpans`. O frontend precisa usar `@grafana/faro-web-tracing` e `TracingInstrumentation`.

**Preciso da env do Mimir?**

Sim, se quiser métricas: `METRICS_ENABLED=true` e `METRICS_REMOTE_WRITE_URL`. Para Elven Cloud, normalmente o token pode ser o mesmo do Loki em `METRICS_REMOTE_WRITE_AUTH_BEARER_TOKEN`.

**Preciso da env do Tempo?**

Sim, se quiser traces: `TRACES_ENABLED=true` e `TRACES_OTLP_HTTP_URL`. Para Elven Cloud, normalmente o token pode ser o mesmo do Loki em `TRACES_OTLP_AUTH_BEARER_TOKEN`.

**Posso usar o mesmo tenant para Loki, Mimir e Tempo?**

Sim. O default é `request`, ou seja, o tenant da rota `/collect/<tenant>/<jwt>` é propagado para os sinks multi-tenant.

**O collector aceita clientes antigos?**

Aceita payload JSON legado já compatível com o collector anterior. Não trate raw logfmt puro como compatível com o endpoint Faro novo.

**Qual é o risco de cardinalidade?**

O collector promove somente labels de baixa cardinalidade para métricas. Dados ricos como URL completa, user id, session id, trace id e span id ficam em Loki, não em labels de Mimir.

**Como rodo uma atualização segura?**

Reexecute o script ou faça rollout Kubernetes com a nova imagem. Valide `/health`, Loki, Mimir e Tempo antes de remover o caminho antigo.


---

# 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/collector-fe-instrumentacao-frontend-com-grafana-faro.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.
