Skip to content

Logging

Conduit writes process logs for startup, configuration changes, control-plane RPCs, export health, and (at debug) per-query summaries. Logs are plain text on stderr or stdout, suitable for journald, Docker log drivers, or a sidecar tail.

Configure logging: to set process log severity and whether lines go to stderr or stdout. The subscriber is always active — omitting logging: still yields info on stderr for lifecycle events (startup, reload, control RPC access). Per-query query complete and query dropped lines are emitted at debug, so default info stays quiet under load. For traffic volume and latency, use Metrics. For full wire copies or phase-by-phase detail on selected queries, see Event export and Tracing.

Process logging is not OTLP logs

logging: controls the Rust tracing subscriber (severity and sink). It is not OpenTelemetry log export over OTLP (not implemented). OTLP metrics use metrics.otel — see Metrics.

logging.level: trace is not pipeline tracing

The config value trace is maximum log verbosity. Per-query pipeline traces are configured under the separate tracing: block — see Tracing.

Configuration

Field reference: Config schema: logging.

When the logging: block is omitted, Conduit uses info level and writes to stderr.

logging:
  level: info       # error | warn | info | debug | trace
  output: stderr    # stderr | stdout
SettingMeaning
logging.levelMinimum severity emitted. Valid values: error, warn, info, debug, trace
logging.outputstderr (default) or stdout

Validate with conduitctl validate --file …. Invalid levels or outputs fail validation before Conduit starts.

Query access — ACL denials

Optional logging.query_access sets an independent level for Client ACL denial lines without raising global logging.level. When omitted, ACL denials stay off at default info. Optional acl_denied_sample (per_source or every_nth) reduces log volume only — metrics and enforcement still count every decision.

Field reference: Config schema: logging.

logging:
  level: info
  query_access:
    acl_denied: warn
    acl_denied_sample:
      mode: per_source
      rate: 10

RUST_LOG override

If the environment variable RUST_LOG is set when Conduit starts, it replaces logging.level for filter construction. This follows common Rust tooling conventions — useful in labs:

RUST_LOG=conduit=debug,conduit_events=trace conduit /path/to/conduit.yaml

Unset RUST_LOG in production unless you intend to override the config file.

Log format

Conduit uses structured tracing output:

  • Each line includes a target (module path, for example conduit_core::configurator).
  • Dynamic string fields (qname, pool names, addresses) pass through log_text — ASCII control characters are stripped so terminals and log shippers see plain text.

ANSI color is disabled so logs stay consistent in files and containers.

flowchart LR
  CFG[logging.level / RUST_LOG] --> SUB[tracing subscriber]
  SUB --> OUT[stderr or stdout]
  DP[Dataplane workers] --> SUB
  CP[Control plane] --> SUB
  EXP[Metrics / event export tasks] --> SUB

Representative log lines

These are the lines operators most often search for. Default info covers lifecycle and control-plane access; per-query lines require debug or higher.

Startup and listeners

After a snapshot is active and listeners bind:

INFO … dataplane startup summary generation=1 listeners=1 pools=1 rules=0 forward_timeout_ms=2000 egress_sources_v4=- egress_sources_v6=- event_sinks=0 events_enabled=false
INFO … configured listener address=127.0.0.1:15353 protocol=udp worker_threads=1
INFO … Starting listening on 127.0.0.1:15353 udp

Use dataplane startup summary to confirm generation, pool/rule counts, forward timeout, egress source lists, and whether event sinks compiled.

Per-query summaries

At debug, each completed transaction emits a structured query complete line (not shown at default info):

DEBUG … query complete txn_id=1 dns_id=… qname=example.com. rcode=NOERROR pool=default backend=127.0.0.1:5300 cache=- attempts=1

The backend field is the backend label — the configured backend name when set, otherwise the ip:port address — matching the identity used in metrics, traces, and event sinks.

cache is the named cache instance when the answer came from cache (for example durable); otherwise - (same sentinel as an unused pool / backend on a cache hit).

Policy drops (no reply sent) log at debug as query dropped and increment conduit_queries_dropped_total:

DEBUG … query dropped txn_id=2 dns_id=… qname=blocked.example.

Enable per-query lines for lab debugging:

logging:
  level: debug
  output: stderr

The txn_id field is the internal id used by conduitctl trace and GetTrace — see Tracing.

Configuration reload and apply

Successful snapshot swaps:

INFO … config applied generation=2 source=sighup

When sections change, Conduit may log concise diffs (pool counts, rule counts, observation sink counts). Listener or forward changes that need a restart log pending (restart required) — see Pending reconcile.

Control plane access

When control: is enabled, each gRPC RPC logs at info as control rpc: method path, peer address, tls (true/false for transport TLS — not the same as requestor mtls), requestor identity (anonymous, api_key, mtls, etc. — never the secret value), gRPC status (grpc_code), and latency. Config RPCs (ApplyConfig, ValidateConfig, ReloadFromFile) also emit a separate control rpc outcome line carrying the application outcome (ok/rejected), error_count, and errors. Successful outcomes log at info; rejections log at warn. Request and response bodies are not logged. A config rejected by validation logs grpc_code=Ok on the transport line and outcome=rejected at warn on the outcome line, because the verdict is returned in-band, not as a transport error.

Failed control-plane connections that never reach an RPC (TCP accept errors, TLS handshake failures) log at warn as control plane connection failed (tls, error, peer when known).

Details: gRPC and conduitctl — Access logs.

Optional pipeline trace JSON

When tracing.output.log_json: true, completed pipeline traces also appear at info with target conduit::trace. See Tracing — JSON log output.

Export and observability tasks

At debug or warn, you may see per-query summaries, OTEL push results, dnstap reconnect warnings, or Rhai sandbox messages. Enable debug temporarily when diagnosing queries, export, or scripting.

Choosing a log level

Level Typical use
error Failures only — startup errors, unrecoverable export failures
warn Auth rejections, rejected config apply/validate/reload (control rpc outcome), collector outages, other subsystem warnings
info Default — startup summary, successful config apply, control rpc (transport), successful control rpc outcome
debug Per-query query complete / query dropped, OTEL push detail, internal lifecycle
trace Maximum verbosity — very noisy; lab use only

Production deployments usually stay at info. Use debug briefly when troubleshooting individual queries, or rely on Metrics for steady-state volume.

Lab smoke test

  1. Start Conduit with a minimal config (for example Minimal configuration) — omit logging: to use defaults.
  2. In the process log, confirm at info:
  3. dataplane startup summary with generation, listener, and pool counts
  4. configured listener and Starting listening on for your DNS address
  5. Send a query: dig @127.0.0.1 -p 15353 +time=3 smoke.example.com A (adjust port).
  6. At default info, the log stays quiet for per-query traffic — no query complete line (by design).
  7. Stop Conduit. Add or change:
logging:
  level: debug
  output: stderr

Restart Conduit and send another query. 6. Expect a DEBUG line query complete with qname, rcode, pool, backend, cache, and txn_id — use txn_id with Tracing when needed.

Unset debug after the lab — production should remain at info unless you are actively investigating.

Changing logging config

The logging: block may appear in the file layer or in an overlay patch (whole-section replace when the overlay includes logging).

The log subscriber is initialized once at process start. Changing level or output requires a process restart after updating config — reload updates the stored config document but does not rebind the active subscriber.