---
title: Monitoramento
description: Como persistir e consultar dados de monitoramento em deployments self-hosted
icon: Activity
---

import Callout from "../../../../../components/ui/Callout.astro";

## Como o monitoramento funciona

O deco Studio inclui um exporter do OpenTelemetry que grava arquivos NDJSON no diretório `DATA_DIR`. Os dados são organizados em três subdiretórios:

- **`/metrics`** — métricas de sistema e da aplicação
- **`/logs`** — entradas de log estruturadas para cada invocação de ferramenta
- **`/traces`** — traces distribuídos das operações MCP

Os arquivos são particionados por organização com a seguinte estrutura de caminho:

```
{DATA_DIR}/{type}/{org_id}/YYYY/MM/DD/HH/{uuid}.ndjson
```

Uma política de retenção de 30 dias com limpeza automática mantém o uso de disco sob controle.

## Persistindo dados de monitoramento (sidecar S3)

Por padrão, os dados de monitoramento ficam no sistema de arquivos local. Para sobreviver a reinicializações de container e habilitar armazenamento centralizado, configure um processo sidecar que sincronize periodicamente o `DATA_DIR` com o S3:

```bash
# Example: sync monitoring data to S3 every 5 minutes
aws s3 sync $DATA_DIR/metrics s3://your-bucket/metrics
aws s3 sync $DATA_DIR/logs s3://your-bucket/logs
aws s3 sync $DATA_DIR/traces s3://your-bucket/traces
```

<Callout type="tip">
  No Kubernetes, execute isso como um container sidecar compartilhando um volume com o container principal do deco Studio.
</Callout>

## Opção A: ClickHouse (recomendado)

Para deployments em produção, o ClickHouse fornece agregações rápidas e escaláveis sobre os dados de monitoramento.

1. Defina a variável de ambiente `CLICKHOUSE_URL` para o endpoint HTTP do seu ClickHouse.
2. Crie tabelas chamadas `monitoring_logs` e `monitoring_metrics` na sua instância do ClickHouse.
3. Quando configurado, a UI de monitoramento consulta o ClickHouse diretamente via HTTP.

```bash
CLICKHOUSE_URL=https://your-clickhouse-instance:8123
```

<Callout type="info">
  ClickHouse é a melhor escolha para produção: lida com grandes volumes de forma eficiente, suporta agregações rápidas e possui integração nativa com S3 para carregar arquivos NDJSON.
</Callout>

## Opção B: DuckDB + S3

Para deployments menores que querem evitar executar um banco de dados separado, você pode usar DuckDB com armazenamento montado via S3.

1. Monte seu bucket S3 como um sistema de arquivos local usando uma ferramenta como **s3fs**, **goofys** ou **mountpoint-s3**.
2. Defina `DATA_DIR` para o caminho montado.
3. O deco Studio grava arquivos NDJSON diretamente no mount, e o engine DuckDB embutido lê do mesmo caminho.

```bash
# Mount S3 bucket
mountpoint-s3 your-bucket /mnt/monitoring

# Point DATA_DIR to the mount
DATA_DIR=/mnt/monitoring
```

Não é necessário definir `CLICKHOUSE_URL` — o DuckDB consulta os arquivos NDJSON em disco.

## Opção C: Google Cloud Storage (OTLP via collector)

Para deployments self-hosted no GCP que querem **sem ClickHouse e sem disco/sidecar**, o Studio pode ler seus dados de monitoramento diretamente de um bucket no GCS. O Studio já emite os dados de monitoramento como logs OTLP padrão pela rede; você os aponta para um OpenTelemetry Collector que os grava no GCS com o exporter `google_cloud_storage`, e o engine DuckDB embutido os lê de volta pelo endpoint compatível com S3 do GCS.

```
Studio ──logs OTLP──▶ OTel Collector ──exporter google_cloud_storage──▶ gs://bucket/<prefix>/...
                              (OTLP JSON, cliente nativo GCS)                  ▲
                                            DuckDB embutido lê + achata na consulta
```

O collector **escreve** com uma service account do Google (cliente nativo do GCS — sem assinatura S3). O Studio **lê** via `httpfs` do DuckDB, que fala a API compatível com S3 do GCS, então precisa de uma chave HMAC. Ambos podem usar a **mesma** service account.

<Callout type="warning">
  **Não** use o exporter `awss3` para GCS. Os checksums de request default do AWS SDK v2 são rejeitados pelo GCS (`SignatureDoesNotMatch`), e o workaround via env **não** tem efeito dentro desse exporter. Use o `google_cloud_storage`.
</Callout>

Começando do zero (ainda sem bucket)? Os passos abaixo usam a CLI `gcloud` — defina `PROJECT_ID` e um `BUCKET` globalmente único primeiro.

**1. Crie o bucket.** (Pré-crie — o exporter reaproveita ele, veja o passo 5.)

```bash
gcloud storage buckets create "gs://${BUCKET}" \
  --project="${PROJECT_ID}" --location=us --uniform-bucket-level-access
```

**2. Crie uma service account e conceda acesso ao bucket.** O `storage.admin` no escopo do bucket cobre o que o exporter precisa (escrita de objetos **e** `storage.buckets.get`, que o `reuse_if_exists` chama). Não precisa de `buckets.create` no projeto.

```bash
gcloud iam service-accounts create studio-monitoring --project="${PROJECT_ID}"
SA="studio-monitoring@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud storage buckets add-iam-policy-binding "gs://${BUCKET}" \
  --member="serviceAccount:${SA}" --role="roles/storage.admin"
```

No GKE, vincule essa SA à Kubernetes SA do collector via **Workload Identity** (sem arquivo de key). Fora do GKE, crie uma key (`gcloud iam service-accounts keys create key.json --iam-account="${SA}"`) e monte com `GOOGLE_APPLICATION_CREDENTIALS`.

**3. Crie uma chave HMAC para a leitura do Studio.** O DuckDB lê via API compatível com S3, que precisa de uma chave HMAC na mesma SA:

```bash
gcloud storage hmac keys create "${SA}" --project="${PROJECT_ID}"
#  → accessId  = MONITORING_S3_ACCESS_KEY_ID  (GOOG1E...)
#  → secret    = MONITORING_S3_SECRET_ACCESS_KEY   (exibido só uma vez)
```

**4. Envie os logs de monitoramento do Studio para o seu collector.** Defina `MONITORING_OTLP_ENDPOINT` (ou habilite o collector no cluster) para que o Studio exporte os logs OTLP para ele.

**5. Configure o collector para gravar OTLP-JSON no GCS.** Adicione o exporter `google_cloud_storage` ao pipeline de **logs** do collector. Ele serializa em OTLP JSON por padrão — exatamente o que o dashboard lê.

```yaml
processors:
  batch:
    # Um objeto é gravado por flush. Mantenha os batches limitados para cada
    # arquivo ficar bem abaixo do limite de 32 MiB por arquivo do leitor, e para
    # limitar quantos objetos cada query do dashboard varre. (send_batch_max_size
    # precisa ser >= send_batch_size.)
    send_batch_size: 2048
    send_batch_max_size: 2048
    timeout: 60s
exporters:
  google_cloud_storage:
    bucket:
      name: your-bucket
      project_id: your-project   # auto-detectado no GKE; obrigatório fora do GCP
      region: us
      reuse_if_exists: true      # usa o bucket pré-criado; necessário p/ sobreviver a restart
      file_prefix: logs
      partition:
        prefix: logs             # o prefixo de leitura (precisa bater com MONITORING_S3_PREFIX)
        format: "year=%Y/month=%m/day=%d/hour=%H"
service:
  pipelines:
    logs:
      processors: [batch]
      exporters: [google_cloud_storage]
```

<Callout type="warning">
  `reuse_if_exists: true` é obrigatório. Com o default (`false`) o exporter tenta **criar** o bucket a cada startup e falha com `409 Conflict` quando ele já existe — então o collector não reinicia.
</Callout>

<Callout type="info">
  O leitor limita um arquivo a 32 MiB; arquivos maiores são ignorados. Os ajustes de `batch` acima mantêm cada objeto bem abaixo disso — limite o `send_batch_max_size` se os inputs das suas ferramentas forem grandes. (Arquivos menores e em menor quantidade também deixam cada query do dashboard mais barata.)
</Callout>

**6. Aponte o leitor do Studio para o mesmo bucket:**

```bash
MONITORING_S3_BUCKET=your-bucket
MONITORING_S3_PREFIX=logs       # bate com o partition.prefix do collector
MONITORING_S3_ENDPOINT=https://storage.googleapis.com
MONITORING_S3_ACCESS_KEY_ID=<hmac-key>
MONITORING_S3_SECRET_ACCESS_KEY=<hmac-secret>
```

Quando `MONITORING_S3_BUCKET` está definida (e `CLICKHOUSE_URL` não), o dashboard lê os arquivos OTLP-JSON do bucket. As métricas (chamadas, erros, percentis de latência) são derivadas dessas mesmas linhas de log, então não há um armazenamento de métricas separado. A extensão `httpfs` que o DuckDB precisa já vem embutida na imagem oficial, então isso funciona mesmo com políticas restritas de rede de saída.

**7. Verifique.** Faça algumas chamadas de ferramenta pelo Studio, depois confirme que os arquivos chegaram (o collector faz flush no timeout do batch) e que o dashboard popula:

```bash
gcloud storage ls --recursive "gs://${BUCKET}/logs/" | head
```

**Retenção (recomendado).** Quando o bucket usa o layout de partição `year=/month=/day=` acima, cada query do dashboard poda a leitura para apenas as partições de dia cobertas pelo intervalo de datas selecionado — não varre mais o prefixo inteiro. A detecção de schema ainda *lista* os objetos sob o prefixo, então uma regra de ciclo de vida no bucket continua sendo a forma prática de limitar esse custo de listagem conforme o histórico cresce. O Studio não aplica retenção sozinho — adicione uma regra pra deletar objetos automaticamente, ex. após 30 dias:

```bash
echo '{"rule":[{"action":{"type":"Delete"},"condition":{"age":30}}]}' > /tmp/lifecycle.json
gcloud storage buckets update "gs://${BUCKET}" --lifecycle-file=/tmp/lifecycle.json
```

<Callout type="info">
  A exportação OTLP limita o payload de `output` de cada chamada de ferramenta em 8&nbsp;KB (igual ao caminho do ClickHouse hospedado). Os **inputs** das ferramentas e todas as análises (contagens, taxa de erro, latência) não são afetados; apenas corpos de resposta muito grandes são cortados no inspetor de chamadas.
</Callout>

### Solução de problemas

- **Collector não inicia / `409 Conflict` no bucket:** defina `bucket.reuse_if_exists: true` e pré-crie o bucket (passo 1).
- **`403 storage.buckets.get denied` no startup:** a service account do collector precisa de `storage.admin` no escopo do bucket (ou ao menos `storage.buckets.get` + escrita de objetos) — veja o passo 2.
- **Dashboard vazio / "Monitoring stats unavailable":** confirme que os objetos existem com `gcloud storage ls "gs://${BUCKET}/logs/"`; e confirme que `MONITORING_S3_PREFIX` **bate exatamente** com o `partition.prefix` do collector.
- **Studio falha ao iniciar com erro de config:** quando `MONITORING_S3_BUCKET` está definida, a access key e o secret HMAC são obrigatórios (o diretório de extensão do DuckDB já vem embutido na imagem oficial).
- **`Malformed JSON in file …`:** um objeto truncado/corrompido sob as partições `year=/…` falha a leitura inteira — o DuckDB não consegue ignorar erros de parse em JSON de objeto único. Delete o objeto citado no erro (normalmente uma escrita parcial de um collector que caiu); uma regra de retenção evita acúmulo. Objetos **fora** do layout `year=/month=/day=` (ex. dumps antigos na raiz do prefixo, ou um esquema de partição antigo) são ignorados automaticamente — a query só lê `year=*/…`.
- **`Out of Memory Error` em `queryMetricTimeseries` / `queryLlmUsageStats`:** o OOM vem de achatar o prefixo inteiro numa query. Com o layout de partição `year=/month=/day=` acima, as queries podam o scan de dados para as partições de dia do intervalo do dashboard, o que evita o problema — então primeiro confirme que o collector escreve esse layout (`gcloud storage ls` deve mostrar caminhos `year=…/month=…/day=…/`) e estreite o intervalo de datas. Se ainda estourar num container pequeno, reduza o paralelismo com `DUCKDB_THREADS` (ex. `2`) — menos threads reduz o pico de memória. Note que `DUCKDB_MEMORY_LIMIT` usa ~80% da RAM do container por padrão e **não pode exceder a memória física**, então aumentá-lo além do que o container tem não ajuda (e reduzi-lo só faz estourar mais cedo) — dê mais memória ao container. Defina também uma regra de retenção/lifecycle no bucket.

## Variáveis de ambiente

| Variável | Padrão | Descrição |
|----------|---------|-------------|
| `DATA_DIR` | `~/deco` | Diretório base para os arquivos NDJSON de monitoramento |
| `CLICKHOUSE_URL` | *(não definida)* | Endpoint HTTP do ClickHouse. Quando definida, a UI de monitoramento usa ClickHouse em vez de DuckDB |
| `MONITORING_OTLP_ENDPOINT` | *(usa `OTEL_EXPORTER_OTLP_ENDPOINT` como fallback)* | Endpoint OTLP para onde o Studio exporta os logs de monitoramento (seu collector) |
| `MONITORING_S3_BUCKET` | *(não definida)* | Bucket no GCS com os logs OTLP-JSON. Quando definida (e `CLICKHOUSE_URL` não), o dashboard lê deste bucket via DuckDB |
| `MONITORING_S3_PREFIX` | *(nenhum)* | Prefixo de chave dentro do bucket (igual ao `s3_prefix` do collector) |
| `MONITORING_S3_ENDPOINT` | usa `S3_ENDPOINT` como fallback | Endpoint compatível com S3, ex. `https://storage.googleapis.com` |
| `MONITORING_S3_REGION` | usa `S3_REGION` como fallback | Região para assinatura SigV4 (`auto` para GCS) |
| `MONITORING_S3_ACCESS_KEY_ID` | usa `S3_ACCESS_KEY_ID` como fallback | Chave HMAC do GCS |
| `MONITORING_S3_SECRET_ACCESS_KEY` | usa `S3_SECRET_ACCESS_KEY` como fallback | Segredo HMAC do GCS |
| `DUCKDB_MEMORY_LIMIT` | *(80% da RAM)* | Limite de memória do engine DuckDB embutido, ex. `2GB`. Reduza em containers com pouca memória |
| `DUCKDB_THREADS` | *(todas as CPUs)* | Número de threads do engine DuckDB embutido. Menos threads reduz o pico de memória |
