Skip to main content

Observability

Omni exports OpenTelemetry traces, metrics, and logs from the five core services: omni-web (Node/SvelteKit), omni-ai (Python/FastAPI), omni-searcher (Rust), omni-indexer (Rust), and omni-connector-manager (Rust). Connectors may capture telemetry individually but are not audited for signal completeness. This document describes the signal architecture, metric inventory, and how to configure an external collector or backend.

Architecture & Signal Flow

Omni does not bundle or require an OpenTelemetry Collector or backend. Signals are sent to whatever endpoint is configured in OTEL_EXPORTER_OTLP_ENDPOINT. The default value is empty, which means instrumentation is active but signals are dropped / not exported or stored. When a non-empty endpoint is set, all three signals (traces, metrics, logs) are sent to ${OTEL_EXPORTER_OTLP_ENDPOINT}/v1/{traces,metrics,logs}. Point OTEL_EXPORTER_OTLP_ENDPOINT at any compatible OTLP/HTTP collector or backend. See Production & Privacy Guidance for production recommendations.

Base OTLP Endpoint Semantics

Resource Attributes

Every signal carries:
  • service.name — the logical service name (omni-searcher, omni-indexer, omni-ai, omni-web, omni-connector-manager)
  • service.version — from SERVICE_VERSION
  • deployment.environment — from OTEL_DEPLOYMENT_ENVIRONMENT
  • deployment.id — from OTEL_DEPLOYMENT_ID

Shutdown Behaviour

On graceful shutdown (SIGTERM/SIGINT), each of the five core services flushes pending spans, metrics, and logs before exiting. In Rust services the OTLP exporter timeout is hard-coded at 10 seconds in the opentelemetry-rust SDK; OTEL_EXPORTER_OTLP_TIMEOUT is not honoured. Busy shutdowns that exceed the Rust SDK timeout may lose the final batch. Python (omni-ai) honours the standard OTLP timeout. Node (omni-web) uses the SDK default.

Trace Continuity

HTTP Request Tracing

Rust services (searcher, indexer, connector-manager) use custom axum middleware. SERVER spans use bounded METHOD /route-template names; custom outbound CLIENT spans use HTTP METHOD. W3C traceparent / tracestate is propagated in HTTP headers. Node (omni-web) and Python (omni-ai) use auto-instrumentation provided by the respective OpenTelemetry SDKs. Span names follow the SDK convention (e.g. HTTP method + route pattern) rather than a unified scheme. Outbound HTTP requests create a CLIENT span, producing parent/child relationships within a single trace (not links) when the web service calls internal APIs. The same traceId is shared across the web→searcher and web→AI call chain.

Async Queue Tracing

The indexer and connector-manager use a Postgres-based queue.
  • Connector-manager enqueues connector events into connector_events_queue with a PRODUCER span, storing traceparent and tracestate in the queue row.
  • Indexer consumes these events. Because the producer and consumer run in different processes and transactions, the consumer creates a new root trace (no parent). A native OTel link is added from the CONSUMER span back to the PRODUCER span via the stored traceparent/tracestate from the queue row.
  • Omni-ai batch processor consumes embedding events from embedding_queue using the same producer→consumer pattern with traceparent/tracestate stored in queue rows and native OTel links.
This allows trace explorers to show the producer→consumer relationship even though they are not in the same trace tree. Queue instrumentation is implemented by the shared Rust queue/telemetry helpers and service consumers, and by services/ai/embeddings/batch_processor.py in Python.

Metric Inventory

All metric names use the omni.* namespace.

HTTP Server RED Metrics (cross-cutting, Rust services)

Recorded via axum middleware in shared/src/metrics.rs:

Indexer Queue Metrics

Recorded in shared/src/metrics.rs:

Searcher Metrics

Recorded in shared/src/metrics.rs:

Connector Sync Metrics

Recorded in shared/src/metrics.rs:

AI / Embedding Metrics

Recorded in services/ai/embeddings/batch_processor.py: No LLM metrics are currently recorded. Metrics for Rust services are defined centrally in shared/src/metrics.rs, which all Rust services link. The AI service instruments embedding metrics locally in batch_processor.py.

Log Correlation

How trace_id and span_id appear in log records depends on the runtime and whether an active OTel span exists at the log call site.

Sanitisation

  • Log body content is sent to the OTLP endpoint exactly as produced by the application. No global PII scrubber is applied.
  • Node’s explicit Pino-to-OTel hook exports only allowlisted primitive log attributes. Its error serializer removes dynamic messages and stacks from structured error fields. Automatic Pino log sending is disabled to prevent duplicate export.
  • Runtime logging call sites were audited to avoid sensitive and high-cardinality fields, but this is not a global scrubber. Future log sites must follow the same policy.

Troubleshooting

Production & Privacy Guidance

When using an external OTLP collector or backend in production:
  1. Use TLS — enable TLS on the OTLP endpoint or use gRPC with TLS.
  2. Authenticate — use API keys or mTLS to secure the connection.
  3. Configure sampling — use a probabilistic_sampler or tail_sampling processor in your collector for high-throughput services.
  4. Adjust batching — tune batch processor timeouts and sizes for your throughput.
  5. Set resource limits — configure memory limits and spike limits in the collector or backend.
  6. Never expose the OTLP endpoint publicly — it has no built-in authentication. Bind to loopback or use a private network.
  7. Filter sensitive data — no PII scrubbing is applied by Omni. Redact sensitive fields in the collector or at the application level before logging.