Observabilidade

Endpoints de health

ServiçoEndpointRetorna
appGET /api/health{"status":"ok","timestamp":"..."}
realtimeGET /health na porta 3002{"status":"ok","timestamp":"...","connections":0}

/api/health é apenas um sinal de liveness. Ele retorna 200 enquanto o processo estiver servindo HTTP — não verifica o banco de dados, o Redis nem o object storage. Uma resposta saudável não significa que o app consegue atender tráfego com sucesso, então não trate isso como verificação de dependências. Verifique as dependências com o teste de fumaça.

Probes do Kubernetes

O chart traz probes ajustados para o cold start do Next.js. Padrões do app:

ProbeCaminhoOrçamento
startupProbe/60 × 5s = 5 minutos para ficar pronto
livenessProbe/6 × 30s = 180s de falha antes de reiniciar
readinessProbe/3 × 10s = ~30s para receber tráfego

O realtime usa /health na porta 3002, com um orçamento de inicialização de 150 segundos.

O orçamento generoso de inicialização importa: um cold start do Next.js com um bundle grande pode levar minutos, e um liveness probe mais apertado reinicia o pod no meio do boot, em loop. Se você customizar os probes, mantenha o orçamento de inicialização bem acima do cold start que você observa.

app:
  startupProbe:
    httpGet:
      path: /
      port: 3000
    periodSeconds: 5
    failureThreshold: 60

Logs

Os dois serviços registram JSON estruturado no stdout. Colete com o que você já usa — Fluent Bit, Vector, Datadog Agent, Loki.

Em builds de produção o logger usa ERROR por padrão, e o chart do Helm não define LOG_LEVEL para o app nem para o realtime. Até você aumentar o nível, só aparecem erros nos logs — e é por isso que um deployment aparentemente saudável pode parecer não registrar nada. Defina LOG_LEVEL: "info" ao colocar um deployment em operação ou ao investigar um problema.

kubectl logs -n studio -l app.kubernetes.io/component=app --tail=200 -f
kubectl logs -n studio -l app.kubernetes.io/component=realtime --tail=200 -f
kubectl logs -n studio deploy/studio-app -c migrations --tail=100
docker compose -f docker-compose.prod.yml logs -f studio

Toda requisição de API carrega um request ID que aparece em todas as linhas de log daquela requisição — o jeito mais rápido de reconstruir uma chamada que falhou.

Os logs de execução de workflow são uma superfície separada, de produto, armazenada no banco de dados e visível em Logs no app. Eles não são a mesma coisa que os logs de contêiner: use os logs de contêiner para problemas de infraestrutura e Logs para o comportamento dos workflows.

Ocultar dados pessoais nos logs

Ative o serviço de PII e a ocultação nos logs se as execuções puderem conter dados sensíveis:

pii:
  enabled: true
app:
  env:
    PII_REDACTION: "true"
    INTERNAL_API_BASE_URL: "http://studio-app.studio.svc.cluster.local:3000"

Veja Segurança para o requisito de INTERNAL_API_BASE_URL — sem um valor acessível dentro do cluster, o caminho falha de forma fechada.

Telemetria anônima

O Studio envia telemetria de uso anônima por padrão. Traces de OpenTelemetry são exportados para https://telemetry.seeyu.ai/v1/traces, a menos que você desative isso. Deployments com auto-hospedagem que têm política de egresso devem decidir sobre isso explicitamente.

O que é coletado, conforme apps/web/telemetry.config.ts: estatísticas de uso de recursos, taxas de erro, métricas de desempenho (amostradas em 10%) e traces de operações de IA/LLM. O que não é coletado: informações pessoais, conteúdo ou saídas de workflow, chaves de API ou tokens, e endereços IP ou geolocalização.

Três formas de mudar isso:

# Disable entirely
NEXT_TELEMETRY_DISABLED=1

# Or redirect to your own OTLP collector instead of Studio's
TELEMETRY_ENDPOINT=http://otel-collector.observability.svc.cluster.local:4318/v1/traces

Cada pessoa também pode desativar individualmente em Settings → Privacy → Allow anonymous telemetry.

Tracing

O Studio emite traces de OpenTelemetry. O chart também pode fazer o deploy de um collector para você:

telemetry:
  enabled: true

Ou aponte o app para um collector que você já mantém:

VariávelFinalidade
OTEL_EXPORTER_OTLP_ENDPOINTEndpoint do collector (OTLP)
OTEL_EXPORTER_OTLP_HEADERSHeaders de autenticação, key=value separados por vírgula
OTEL_TRACES_SAMPLER_ARGTaxa de amostragem
OTEL_DEPLOYMENT_ENVIRONMENTRótulo de ambiente nos spans emitidos
TELEMETRY_SAMPLING_RATIOTaxa de amostragem no nível da aplicação
TELEMETRY_ENDPOINTEndpoint de telemetria customizado

Especificamente para o Grafana Cloud:

VariávelFinalidade
GRAFANA_OTLP_ENDPOINTEndpoint OTLP do Grafana
GRAFANA_OTLP_HEADERSPor exemplo Authorization=Basic <base64(instanceId:token)>
GRAFANA_DEPLOYMENT_ENVIRONMENTRótulo do tier de deployment

Se você ativar a exportação para o Jaeger no chart, aponte telemetry.jaeger.endpoint para a porta OTLP gRPC (4317) do Jaeger — o collector exporta via OTLP.

Métricas

As imagens padrão do app e do realtime não expõem um endpoint /metrics. A opção monitoring.serviceMonitor do chart existe para builds que expõem — ativá-la com as imagens de fábrica gera um ServiceMonitor que não coleta nada.

Até que um endpoint de métricas da aplicação exista, construa os alertas a partir dos sinais que já existem:

  • Estado do Kubernetes — reinícios de pod, CrashLoopBackOff, OOMKills, contagem de réplicas versus desejada, uso de PVC (kube-state-metrics).
  • Ingress/load balancer — taxa de requisições, taxa de 5xx, latência p99, contagem de conexões websocket.
  • PostgreSQL — número de conexões versus max_connections, atraso de replicação, uso de disco, queries de longa duração.
  • Redis — uso de memória, evictions, clientes conectados.
  • CronJobs — última conclusão bem-sucedida de cada job.

O que monitorar com alertas

AlertaPor que importa
Loop de reinício do pod do app / OOMKilledMemória é o recurso limitante; OOMKills significam que execuções estão morrendo no meio
Um CronJob não teve sucesso em cerca de 3× o próprio intervalo de agendamentoWorkflows agendados e triggers de polling estão mortos silenciosamente. Defina o limite por job — os jobs de cada minuto justificam ~15 minutos; os de hora em hora, duas vezes ao dia e diários precisam de janelas proporcionalmente maiores
Taxa de 5xx no ingress acima da linha de baseImpacto amplo nas pessoas
Conexões do Postgres acima de 80% de max_connectionsA próxima réplica ou pico de tráfego vai começar a falhar
Disco do Postgres acima de 80%Os embeddings da base de conhecimento crescem de forma constante
Redis inacessívelA colaboração ao vivo e as atualizações de status param, sem erros no app
Certificado expirando em 14 diasEspecialmente com certificados gerenciados manualmente
Taxa de 4xx/5xx no object storageUploads quebrados normalmente aparecem aqui primeiro

O alerta de CronJob é o que mais falta e o de que mais se precisa. Falhas de jobs em background não produzem erro visível — os agendamentos simplesmente param de disparar. Alerte quando kube_cronjob_status_last_successful_time estiver atrasado, com um limite por job derivado do agendamento daquele job.

Diagnóstico rápido

# Overall state
kubectl get pods,cronjobs -n studio

# Why is a pod unhealthy
kubectl describe pod -n studio <pod>

# Did migrations succeed
kubectl logs -n studio deploy/studio-app -c migrations --tail=100

# Are background jobs running
kubectl get jobs -n studio --sort-by=.metadata.creationTimestamp | tail

# Resource pressure
kubectl top pods -n studio

Common Questions

Sim. Traces anônimos de OpenTelemetry vão para https://telemetry.seeyu.ai/v1/traces, a menos que você defina NEXT_TELEMETRY_DISABLED=1 ou aponte TELEMETRY_ENDPOINT para o seu próprio collector. Isso exclui informações pessoais, conteúdo de workflow, chaves de API e endereços IP, mas é tráfego de saída sobre o qual você deve decidir deliberadamente em um deployment com auto-hospedagem.
As imagens padrão não expõem um endpoint /metrics. A opção monitoring.serviceMonitor do chart existe para builds que expõem; com as imagens de fábrica ela não coleta nada. Construa os alertas a partir dos sinais do Kubernetes, do ingress, do Postgres e do Redis.
Um cold start do Next.js com um bundle grande pode levar minutos. O orçamento de 5 minutos evita que o liveness probe reinicie o pod no meio do boot, o que criaria um loop infinito. Mantenha o orçamento acima do cold start que você observa se customizar os probes.
Toda requisição carrega um request ID que aparece em todas as linhas de log daquela requisição. Encontre-o na resposta ou na primeira linha de log e faça grep por ele no seu agregador de logs.