Logs
The control plane and the daemon both write structured JSON logs to stderr.CUSTOS_LOG_LEVEL
selects debug, info, warn, or error; the default is info.
Logs emitted inside a traced operation carry trace_id and span_id, so a request can be followed
from a log record into its trace.
Traces and metrics
Both binaries create OpenTelemetry traces for HTTP requests, PostgreSQL calls, outbound HTTP calls, daemon authentication, and discrete WebSocket message operations. A small set of application metrics is aggregated in memory. On the control plane, OTLP export is disabled until you set an endpoint:OTEL_METRIC_EXPORT_INTERVAL overrides the default 60-second metric export interval, in
milliseconds, for both the control plane and the daemon.
How daemon telemetry gets out
Daemons never connect to an OTLP backend and open no additional connection. They batch standard OTLP protobufs and forward only the signals the control plane has enabled, over the existing authenticated WebSocket. The control plane relays those batches to the same signal-specific destinations it uses for its own telemetry. Daemon telemetry carries the enrolledhost.id as a resource attribute, so fleet-wide queries can
group by host.
Design properties worth knowing when you are debugging a quiet fleet:
- Core messages and telemetry sit in separate bounded queues. The writer gives core messages priority and drops telemetry rather than blocking Custos when a telemetry queue fills.
- Ping/pong is independent of both, so keepalives are never starved by telemetry volume.
- Telemetry is best-effort and never persists to the daemon’s state directory.
Grafana
A reusable dashboard and its import instructions ship in the repository:A note on collector placement
When the control plane runs directly on the host alongside a collector,127.0.0.1:4318 reaches it.
A containerized control plane needs the collector’s service name on the shared telemetry network
instead, for example http://alloy:4318.