Skip to content

Sinks

A sink delivers encoded bytes to a destination. Set the destination with the sink.type field. The default is stdout.

Most sinks buffer data before delivery, which affects when output appears. For the full model, see Sink Batching.

Choosing the right url:

Network sinks (http_push, remote_write, loki, otlp_grpc) accept a URL resolved inside the process that runs the scenario. The same YAML run from your host CLI versus POSTed to a containerized sonda-server reaches different hosts. See Endpoints and networking for when to use localhost, Compose service names, or Kubernetes Service DNS.

stdout

Writes events to standard output through a buffered writer. This is the default sink.

No additional parameters.

Stdout sink
sink:
  type: stdout

file

Writes events to a file. Parent directories are created automatically.

Parameter Type Required Description
path string yes Filesystem path to write to.
File sink
sink:
  type: file
  path: /tmp/sonda-output.txt

The -o flag on sonda run is a shortcut for --sink file --endpoint <path>:

cpu-scenario.yaml
sonda run cpu-scenario.yaml -o /tmp/metrics.txt

tcp

Writes events over a persistent TCP connection. The connection opens when the scenario starts.

Parameter Type Required Description
address string yes Remote address in host:port format.
TCP sink
sink:
  type: tcp
  address: "127.0.0.1:9999"

udp

Sends each encoded event as a single UDP datagram. There is no persistent connection. An ephemeral local port is bound and each event goes out through send_to.

Parameter Type Required Description
address string yes Remote address in host:port format.
UDP sink
sink:
  type: udp
  address: "127.0.0.1:9999"

memory

In-process buffer sink for tests and benchmarks. Encoded bytes accumulate in a Vec<u8> inside the running process; no I/O happens, no destination is contacted. Use this when you want to drive a scenario through Sonda's pipeline (generatorencodersink) and either ignore the output or inspect it from a test harness that holds a MemorySink directly.

When capture: true, each write records the Instant it landed alongside the bytes, keeping a ring of the most recent max_events entries (default 1_000_000). The ring drops the oldest entry on overflow, so a benchmark that runs longer than the cap allows still ends up with the latest events for drift or rate analysis.

Parameter Type Required Default Description
capture boolean no false Record (Instant, bytes) per write.
max_events integer no 1_000_000 Cap on retained captured events when capture: true.
Memory sink (capture disabled)
sink:
  type: memory
Memory sink with capture for drift measurement
sink:
  type: memory
  capture: true
  max_events: 100000

Warning

This sink is for in-process inspection only — there is no destination to deliver to, so it should never be used in production scenarios. Pair it with a Rust test or benchmark harness that holds the MemorySink instance and reads MemorySink::captured() after the run.

http_push

Note

This sink requires the http Cargo feature flag. Pre-built release binaries include this feature. To build from source: cargo build --features http -p sonda.

Buffers encoded events and delivers them by HTTP POST. Events accumulate until the batch size is reached, then the buffer is flushed as a single POST.

Parameter Type Required Default Description
url string yes -- Target URL for HTTP POST requests.
content_type string no application/octet-stream Value for the Content-Type header.
batch_size integer no 4096 (4 KiB) Size flush threshold in bytes. Raise this for high-rate scenarios.
max_buffer_age duration string no 5s Time flush threshold. A non-empty batch is flushed once buffered longer than this. Set "0s" to disable. See Sink Batching.
headers map no none Extra HTTP headers sent with every request.
HTTP push sink
sink:
  type: http_push
  url: "http://localhost:8428/api/v1/import/prometheus"
  content_type: "text/plain"
  batch_size: 32768

CLI overridesonda run accepts --sink http_push and --endpoint to override the file's sink type and URL on a one-off run. Settings like content_type, batch_size, and custom headers: live in the YAML:

cpu-scenario.yaml
sonda run cpu-scenario.yaml \
  --sink http_push --endpoint http://localhost:8428/api/v1/import/prometheus

Pushing to VictoriaMetrics

VictoriaMetrics via HTTP push
encoder:
  type: prometheus_text
sink:
  type: http_push
  url: "http://localhost:8428/api/v1/import/prometheus"
  content_type: "text/plain"

Custom headers

Use the headers map for protocols that require specific headers:

HTTP push with custom headers
sink:
  type: http_push
  url: "http://localhost:8428/api/v1/write"
  headers:
    Content-Type: "application/x-protobuf"
    Content-Encoding: "snappy"
    X-Prometheus-Remote-Write-Version: "0.1.0"

remote_write

Batches metrics as Prometheus remote write requests. Pair it with the remote_write encoder, which produces length-prefixed protobuf TimeSeries bytes. The sink accumulates entries and, on flush, wraps them in a WriteRequest, applies snappy compression, and HTTP POSTs with the correct protocol headers.

Note

This sink requires the remote-write Cargo feature flag. Pre-built release binaries include this feature. To build from source: cargo build --features remote-write -p sonda.

Parameter Type Required Default Description
url string yes -- Remote write endpoint URL.
batch_size integer no 5 Size flush threshold in number of TimeSeries entries. Raise this for high-rate scenarios.
max_buffer_age duration string no 5s Time flush threshold. A non-empty batch is flushed once buffered longer than this. Set "0s" to disable. See Sink Batching.
Remote write sink
encoder:
  type: remote_write
sink:
  type: remote_write
  url: "http://localhost:8428/api/v1/write"
  batch_size: 5

CLI overridesonda run accepts --encoder remote_write, --sink remote_write, and --endpoint <url> to override the file's sink and encoder on a one-off run:

cpu-scenario.yaml
sonda run cpu-scenario.yaml \
  --encoder remote_write \
  --sink remote_write --endpoint http://localhost:8428/api/v1/write

Compatible endpoints:

Backend URL
VictoriaMetrics http://host:8428/api/v1/write
vmagent http://host:8429/api/v1/write
Prometheus http://host:9090/api/v1/write
Thanos Receive http://host:19291/api/v1/receive
Cortex / Mimir http://host:9009/api/v1/push

kafka

Batches events and publishes them to a Kafka topic. Uses a pure-Rust Kafka client (rskafka) for static binary compatibility. No C dependencies or OpenSSL are required.

Note

This sink requires the kafka Cargo feature flag. Pre-built release binaries include this feature. To build from source: cargo build --features kafka -p sonda.

Parameter Type Required Default Description
brokers string yes -- Comma-separated broker addresses (e.g. "broker1:9092,broker2:9092").
topic string yes -- Kafka topic name.
max_buffer_age duration string no 5s Time flush threshold. A non-empty batch is flushed once buffered longer than this. Set "0s" to disable. See Sink Batching.
tls object no none TLS encryption settings. See TLS.
sasl object no none SASL authentication settings. See SASL.
Kafka sink (plaintext)
sink:
  type: kafka
  brokers: "127.0.0.1:9092"
  topic: sonda-metrics

Kafka brokers and topic live in the YAML. sonda run does not expose flags for them. Override the sink type from the command line with --sink kafka and define the rest in the file:

kafka-cpu.yaml
sonda run kafka-cpu.yaml

Events are buffered and published as a single Kafka record to partition 0 of the configured topic. The size threshold is a fixed 64 KiB internal buffer and is not user-tunable. max_buffer_age (default 5s) is the configurable knob: a non-empty batch is flushed once buffered longer than that. Broker-side auto-topic-creation is supported. The sink retries metadata lookups, which gives the broker time to create the topic if auto.create.topics.enable=true.

TLS

Most managed Kafka services (Confluent Cloud, AWS MSK, Aiven) require TLS-encrypted connections. Add a tls block to enable encryption.

Field Type Required Default Description
tls.enabled boolean yes false Set to true to connect over TLS.
tls.ca_cert string no Mozilla bundled roots Path to a PEM-encoded CA certificate file. Use this for self-signed or internal CAs.
Kafka with TLS
sink:
  type: kafka
  brokers: "broker.example.com:9093"
  topic: sonda-metrics
  tls:
    enabled: true

When ca_cert is omitted, Sonda trusts Mozilla's bundled root certificates (via webpki-roots). This works by default for any broker whose certificate is signed by a public CA.

For brokers with self-signed or internal CA certificates, point ca_cert at the PEM file:

Kafka with custom CA
sink:
  type: kafka
  brokers: "kafka-internal.corp:9093"
  topic: sonda-metrics
  tls:
    enabled: true
    ca_cert: /etc/ssl/certs/internal-ca.pem

Tip

TLS uses rustls (pure Rust). There is no dependency on OpenSSL. The static musl binary stays fully self-contained.

SASL

SASL authenticates your client to the Kafka broker. Sonda supports three mechanisms:

Mechanism When to use
PLAIN Confluent Cloud, Aiven, most SaaS Kafka services.
SCRAM-SHA-256 AWS MSK (serverless and provisioned with SCRAM enabled).
SCRAM-SHA-512 Self-managed Kafka clusters that prefer stronger hashing.
Field Type Required Description
sasl.mechanism string yes "PLAIN", "SCRAM-SHA-256", or "SCRAM-SHA-512".
sasl.username string yes SASL username (API key for Confluent Cloud).
sasl.password string yes SASL password (API secret for Confluent Cloud).
Kafka with SASL PLAIN
sink:
  type: kafka
  brokers: "broker.example.com:9093"
  topic: sonda-metrics
  tls:
    enabled: true
  sasl:
    mechanism: PLAIN
    username: sonda
    password: changeme

Warning

SASL can be used without TLS, but Sonda prints a warning because the credentials are sent in plaintext over the network. Always enable TLS alongside SASL in production.

Common configurations

Confluent Cloud uses TLS and SASL PLAIN. Your API key is the username; the API secret is the password.

confluent-cloud.yaml
sink:
  type: kafka
  brokers: "pkc-xxxxx.us-east-1.aws.confluent.cloud:9092"
  topic: sonda-metrics
  tls:
    enabled: true
  sasl:
    mechanism: PLAIN
    username: YOUR_API_KEY
    password: YOUR_API_SECRET

AWS MSK with SASL/SCRAM authentication uses port 9096.

aws-msk-scram.yaml
sink:
  type: kafka
  brokers: "b-1.mycluster.xxxxx.kafka.us-east-1.amazonaws.com:9096"
  topic: sonda-metrics
  tls:
    enabled: true
  sasl:
    mechanism: SCRAM-SHA-256
    username: msk-user
    password: msk-password

Self-managed Kafka cluster with an internal CA certificate and SCRAM auth.

internal-kafka.yaml
sink:
  type: kafka
  brokers: "kafka-01.internal:9093,kafka-02.internal:9093"
  topic: sonda-metrics
  tls:
    enabled: true
    ca_cert: /etc/ssl/certs/kafka-ca.pem
  sasl:
    mechanism: SCRAM-SHA-512
    username: sonda
    password: s3cret

Some clusters use TLS for encryption but rely on network-level access control instead of SASL.

tls-only.yaml
sink:
  type: kafka
  brokers: "kafka.private:9093"
  topic: sonda-metrics
  tls:
    enabled: true

Info

TLS and SASL are configured via scenario YAML only. There are no CLI flags for these options. Use a scenario file with --scenario.

loki

Note

This sink requires the http Cargo feature flag. Pre-built release binaries include this feature. To build from source: cargo build --features http -p sonda.

Batches log lines and delivers them to Grafana Loki by HTTP POST. A Loki stream is the unit Loki indexes by, identified by its label set. At each flush, entries are grouped by their combined label set (scenario-level labels merged with any per-event dynamic_labels) and sent as a single push containing one stream per unique combination.

Scenario-level labels: form the base stream labels. Per-event dynamic_labels join the stream label set, so a rotation through N values produces N distinct streams, each queryable in Grafana by label. See Dynamic Labels — Loki sinks for the worked pattern.

Parameter Type Required Default Description
url string yes -- Base URL of the Loki instance.
batch_size integer no 5 Size flush threshold in number of log entries. Raise this for high-rate scenarios.
max_streams_per_push integer no 128 Hard cap on unique streams per flush. A flush that would emit more streams than the cap fails with a message naming the offending count and the cap. High-cardinality dynamic_labels configurations surface as a config error instead of overloading the Loki ingester. Raise this if your ingester is sized for higher cardinality.
max_buffer_age duration string no 5s Time flush threshold. A non-empty batch is flushed once buffered longer than this. Set "0s" to disable. See Sink Batching.
Loki sink with top-level labels
version: 2
kind: runnable

defaults:
  rate: 10
  sink:
    type: loki
    url: "http://localhost:3100"
    batch_size: 50

scenarios:
  - signal_type: logs
    name: app_logs_loki
    labels:
      job: sonda
      env: dev

CLI overridesonda run accepts --sink loki, --endpoint <url>, and --label k=v (repeatable) for stream labels:

app-logs.yaml
sonda run app-logs.yaml \
  --sink loki --endpoint http://localhost:3100 \
  --label job=sonda --label env=dev

The sink POSTs to {url}/loki/api/v1/push.

otlp_grpc

Batches OTLP protobuf data and delivers it by gRPC to an OpenTelemetry Collector. Pair it with the otlp encoder, which produces length-prefixed protobuf Metric or LogRecord bytes. The sink accumulates entries and, on flush or when batch_size is reached, wraps them in an ExportMetricsServiceRequest or ExportLogsServiceRequest and sends them by gRPC unary call.

Feature flag and build requirement

This sink requires the otlp Cargo feature flag. Pre-built release binaries and Docker images do not include this feature. Build from source: cargo build --features otlp -p sonda.

Parameter Type Required Default Description
endpoint string yes -- gRPC endpoint URL of the OTEL Collector (e.g. "http://localhost:4317").
signal_type string yes -- "metrics" or "logs". Must match the scenario signal type.
batch_size integer no 5 Size flush threshold in number of data points or log records. Raise this for high-rate scenarios.
max_buffer_age duration string no 5s Time flush threshold. A non-empty batch is flushed once buffered longer than this. Set "0s" to disable. See Sink Batching.
OTLP gRPC sink (metrics)
encoder:
  type: otlp
sink:
  type: otlp_grpc
  endpoint: "http://localhost:4317"
  signal_type: metrics
  batch_size: 5
OTLP gRPC sink (logs)
encoder:
  type: otlp
sink:
  type: otlp_grpc
  endpoint: "http://localhost:4317"
  signal_type: logs
  batch_size: 50

CLI overridesonda run accepts --encoder otlp, --sink otlp_grpc, and --endpoint <url>. The OTLP signal_type is derived from the scenario's signal_type:, so it does not need a separate CLI flag:

cpu.yaml
sonda run cpu.yaml \
  --encoder otlp --sink otlp_grpc --endpoint http://localhost:4317

Scenario-level labels are converted to OTLP Resource attributes, so they appear as resource metadata in the Collector's output.

Compatible receivers:

Backend Default gRPC port
OpenTelemetry Collector 4317
Grafana Alloy 4317
Datadog Agent (OTLP) 4317
Elastic APM Server 8200

Retry with backoff

Network sinks can hit transient failures — connection resets, HTTP 5xx responses, broker outages. By default, Sonda does not retry, which is ideal for CI pipelines where fast failure feedback matters. For long-running soak tests or Kubernetes deployments where short interruptions are expected, add a retry: block to any network sink.

Retry applies to these sinks: http_push, remote_write, loki, otlp_grpc, kafka, tcp. It does not apply to stdout, file, or udp.

Configuration

Add the retry: block inside any network sink definition:

Retry configuration
sink:
  type: http_push
  url: "http://victoriametrics:8428/api/v1/import/prometheus"
  retry:
    max_attempts: 3          # retries after initial failure (total = 4 attempts)
    initial_backoff: 100ms   # first retry delay
    max_backoff: 5s          # backoff cap

All three fields are required when retry: is present:

Field Type Description
max_attempts integer Number of retry attempts after the initial failure. Must be at least 1. Total calls = max_attempts + 1.
initial_backoff duration string Delay before the first retry (e.g. 100ms, 1s).
max_backoff duration string Upper bound on any single backoff delay. Must be at least initial_backoff.

How it works

Sonda uses exponential backoff with full jitter:

base  = min(max_backoff, initial_backoff * 2^attempt)
sleep = random(0, base)

The jitter prevents synchronized retries when several sinks retry at the same time ("thundering herd"). Each retry attempt is logged to stderr:

sonda: retry 1/3 after 127ms (error: connection refused)
sonda: retry 2/3 after 312ms (error: connection refused)
sonda: all 3 retries exhausted (last error: connection refused)

If every retry is exhausted, the batch is discarded. Sonda generates synthetic data, so losing a batch is acceptable and prevents unbounded buffer growth.

Error classification

Not every error triggers a retry. Each sink classifies errors as retryable (transient) or permanent:

Sink Retryable Not retried
http_push, remote_write, loki HTTP 5xx, 429 (rate limit), transport errors HTTP 4xx (except 429)
otlp_grpc UNAVAILABLE, DEADLINE_EXCEEDED, RESOURCE_EXHAUSTED INVALID_ARGUMENT, UNAUTHENTICATED
kafka All produce errors (typically transient broker or network issues) --
tcp Connection reset, broken pipe (reconnects automatically) --

Warning

Permanent errors (a 401 Unauthorized or a malformed request that returns 400) are never retried. Sonda logs a warning and drops the batch immediately.

Retry settings live in the scenario YAML. There is no CLI shortcut for them. Edit the retry: block inside the sink definition to tune attempts and backoff.