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.
file¶
Writes events to a file. Parent directories are created automatically.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | yes | Filesystem path to write to. |
The -o flag on sonda run is a shortcut for --sink file --endpoint <path>:
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. |
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. |
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 (generator → encoder → sink) 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. |
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. |
sink:
type: http_push
url: "http://localhost:8428/api/v1/import/prometheus"
content_type: "text/plain"
batch_size: 32768
CLI override — sonda 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:
sonda run cpu-scenario.yaml \
--sink http_push --endpoint http://localhost:8428/api/v1/import/prometheus
Pushing to VictoriaMetrics¶
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:
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. |
encoder:
type: remote_write
sink:
type: remote_write
url: "http://localhost:8428/api/v1/write"
batch_size: 5
CLI override — sonda run accepts --encoder remote_write, --sink remote_write, and --endpoint <url> to override the file's sink and encoder on a one-off run:
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 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:
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. |
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:
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). |
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.
AWS MSK with SASL/SCRAM authentication uses port 9096.
Self-managed Kafka cluster with an internal CA certificate and SCRAM auth.
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. |
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 override — sonda run accepts --sink loki, --endpoint <url>, and --label k=v (repeatable) for stream labels:
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. |
encoder:
type: otlp
sink:
type: otlp_grpc
endpoint: "http://localhost:4317"
signal_type: metrics
batch_size: 5
encoder:
type: otlp
sink:
type: otlp_grpc
endpoint: "http://localhost:4317"
signal_type: logs
batch_size: 50
CLI override — sonda 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:
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:
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:
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.