Pular para o conteúdo

Observabilidade

O Valter inclui logging JSON estruturado, 30+ metricas Prometheus e tracing OpenTelemetry. A instrumentacao e implementada em tres modulos em src/valter/observability/: logging.py, metrics.py e tracing.py.

O Valter usa structlog para logging JSON estruturado. Cada entrada de log e parseavel por maquina e inclui campos contextuais para correlacao.

# From observability/logging.py
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.StackInfoRenderer(),
structlog.dev.set_exc_info,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
logger_factory=structlog.PrintLoggerFactory(file=sys.stderr),
)

Decisoes de design principais:

  • Formato JSON para stdout/stderr — o Railway captura logs automaticamente
  • Logs vao para stderr para nao interferirem com o transporte MCP stdio (que usa stdout para JSON-RPC)
  • Variaveis de contexto via structlog.contextvars.merge_contextvars injetam trace_id e outros dados de escopo de requisicao em toda entrada de log
  • Nivel de log configuravel via VALTER_LOG_LEVEL (padrao: INFO)

Toda requisicao recebida gera um trace_id que e injetado nas variaveis de contexto e propagado por todas as entradas de log daquela requisicao. Isso permite correlacionar logs ao longo de todo o ciclo de vida da requisicao — do handler da API, passando por queries ao store, ate a serializacao da resposta.

O Valter define 30+ metricas Prometheus usando a biblioteca prometheus_client. As metricas sao expostas via GET /metrics, com acesso restrito por VALTER_METRICS_IP_ALLOWLIST.

MetricaTipoLabelsDescricao
valter_request_duration_secondsHistogramendpoint, method, statusDuracao da requisicao com buckets de 10ms a 10s
valter_requests_totalCounterendpoint, method, statusContagem total de requisicoes
MetricaTipoLabelsDescricao
valter_mcp_rpc_requests_totalCounterrpc_method, tool, status_classTotal de requisicoes JSON-RPC MCP
valter_mcp_rpc_duration_secondsHistogramrpc_method, tool, status_classDuracao de requisicoes MCP (buckets ate 60s)
valter_mcp_tool_calls_totalCountertool, outcomeContagem de chamadas de tool por resultado
valter_mcp_tool_call_duration_secondsHistogramtool, outcomeDuracao de chamadas de tool
valter_mcp_auth_failures_totalCounterreasonFalhas de autenticacao
valter_mcp_rate_limit_blocks_totalCounterRequisicoes MCP bloqueadas por rate limit
MetricaTipoLabelsDescricao
valter_api_rate_limit_blocks_totalCounterreasonRequisicoes de API bloqueadas por rate limit
valter_api_rate_limit_failsafe_blocks_totalCounterRequisicoes de API bloqueadas quando o backend de rate limit esta indisponivel
valter_mcp_rate_limit_failsafe_blocks_totalCounterRequisicoes MCP bloqueadas quando o backend de rate limit esta indisponivel
valter_rate_limit_redis_errors_totalCountersurfaceErros do Redis no middleware de rate limiting
MetricaTipoLabelsDescricao
valter_cache_hits_totalCounterstoreContagem de cache hits
valter_cache_misses_totalCounterstoreContagem de cache misses
MetricaTipoLabelsDescricao
valter_store_healthGaugestoreSaude por store (1=up, 0=down)
valter_queue_depthGaugequeueJobs pendentes na fila ARQ
MetricaTipoLabelsDescricao
valter_ingest_stage_duration_secondsHistogramstage, statusDuracao por estagio de ingestao
valter_artifact_put_latency_msHistogrambackendLatencia de escrita de artefato
valter_artifact_get_latency_msHistogrambackendLatencia de leitura de artefato
valter_artifact_put_fail_totalCounterbackend, content_typeEscritas de artefato com falha
valter_presign_issued_totalCounterbackendContagem de geracao de URLs assinadas
MetricaTipoLabelsDescricao
valter_kg_boost_errors_totalCountersourceErros de KG boost
valter_kg_enrichment_totalCounteroutcomeTentativas de enriquecimento via KG
valter_kg_boost_candidates_totalCounterstrategyResultados de busca elegiveis para KG boost
valter_kg_boost_enriched_totalCounterstrategyResultados de busca efetivamente enriquecidos pelo KG boost
valter_kg_boost_scoreHistogramDistribuicao dos scores brutos de KG boost
MetricaTipoLabelsDescricao
valter_operation_failures_totalCountersurface, stage, error_classFalhas operacionais com taxonomia de baixa cardinalidade

O tracing OpenTelemetry e configurado em observability/tracing.py com auto-instrumentacao do FastAPI.

# From observability/tracing.py
def setup_tracing(service_name: str = "valter") -> None:
resource = Resource.create({"service.name": service_name})
provider = TracerProvider(resource=resource)
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)

A configuracao atual exporta spans para o console via ConsoleSpanExporter. Isso e util para desenvolvimento, mas nao para monitoramento em producao.

# From observability/tracing.py
def instrument_fastapi_app(app: FastAPI) -> None:
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
FastAPIInstrumentor.instrument_app(app)

Isso auto-instrumenta todos os endpoints FastAPI com propagacao de contexto de trace. A instrumentacao e idempotente — chama-la varias vezes no mesmo app nao tem efeito.

Endpoint: GET /v1/health

O endpoint de health check verifica a conectividade com todos os stores de backend com timeout de 5 segundos por store:

StoreO Que Verifica
qdrantConectividade com o vector store
neo4jConectividade com o banco de dados de grafo
postgresConectividade com o document store
redisConectividade com o cache store
artifact_storageBackend de artefatos (local ou R2)

Cada store retorna up ou down com latencia medida em milissegundos. O status geral e healthy quando todos os stores estao ativos, ou degraded quando algum store esta indisponivel. A resposta tambem inclui o numero da versao do Valter e o uptime.

A saude dos stores e rastreada pelo gauge Prometheus valter_store_health, permitindo que sistemas de monitoramento detectem degradacao ao longo do tempo.

Seis regras de alerta estao definidas na documentacao do projeto, cobrindo:

  • Latencia p95 excedendo o limite
  • Taxa de erros excedendo o limite
  • Degradacao da saude dos stores
  • Queda na taxa de cache hit
  • Aumento na profundidade da fila
  • Picos de erros de KG boost