Where the agent sends data
Where the agent sends data
Section titled “Where the agent sends data”The senders stage of a collection config, and every destination the shipped
agent actually supports.
The shape of it
Section titled “The shape of it”A collection config is readers → transformers → senders. A sender names its destination and carries that destination’s settings:
senders: - sondar: repo: app_logs sourcetype: json url: - https://sondar.internal:9200 token: "<collect token>"More than one sender can be listed; each receives every event the readers produce, after transformers. That is the supported way to fan out — send the same stream to Sondar and to a Kafka topic, for example.
Destinations
Section titled “Destinations”Into Sondar
Section titled “Into Sondar”| name | what it is |
|---|---|
sondar |
log and metric events into an index. The one you want almost always. |
sondar_event |
discrete events rather than log lines |
sondar_topo |
topology relationships |
sondar needs repo (the index), sourcetype, one or more url, and a
token. Get the token wrong and the agent retries with
response code: 401 … Access denied, unauthenticated in its own log and
nothing anywhere else — no data arrives and the server never learns why, so
check the agent log first when an index stays empty.
To another system
Section titled “To another system”| name | notes |
|---|---|
elasticsearch |
for teams keeping an existing ES cluster in the path |
kafka |
the usual choice for fanning out to other consumers |
http |
POST to an arbitrary endpoint |
syslog |
forward as syslog |
opentelemetry |
OTLP. Sondar’s own APM ingest is OTLP on 4317 |
openmetrics |
expose metrics for scraping rather than pushing |
To storage
Section titled “To storage”| name | notes |
|---|---|
s3 |
S3-compatible object storage |
file |
a local file |
archive_local, archive_s3, archive_sftp |
the archive family — write-once copies for retention and compliance |
If you have a “keep an immutable copy for N years” requirement, this is the mechanism — worth knowing it exists before designing something else.
To a database
Section titled “To a database”One sender per engine: mysql, postgres, oracle, mssql, dm.
Each needs a table_name; omit it and the runner refuses to start with
load config failed: table_name is a required field. Writing logs to a
relational database is rarely the right call for volume, but it is the usual
answer when a downstream report has to join against existing tables.
Development only
Section titled “Development only”discard throws events away, example and test exist for the agent’s own
tests. Useful when you want to run a config for its readers and transformers
without a destination — discard is the honest way to do that.
Practical notes
Section titled “Practical notes”A sender name the agent does not know is not a loud failure. Before August 2026 the shipped collection configs named a sender the agent lacked, and the server’s resolution of that config silently returned nothing. Both spellings are now accepted and the agent keeps a deprecated alias, but the lesson stands: if an index is empty, confirm the sender name is one of the above before looking anywhere else.
Config errors are reported by the agent, not the UI. A missing required field stops the runner loading and is written to the agent’s log with the reason. As of August 2026 those messages are English; earlier builds emitted them in Chinese.
Oversized lines are handled differently at each end. The agent drops a line
over max_line_len (1 MB by default) and logs a warning; the server truncates
at 100,000 characters silently. A line between the two arrives cut in half with
no indication. See indexes-and-field-extraction.md.
Into Sondar, precisely
Section titled “Into Sondar, precisely”The sondar sender is the default destination and the one with the most
moving parts. Verified defaults: gzip: true, escape_html: true,
repo: default, non_retryable_statuscodes: [400]. HTTP tuning nests under
client: (timeouts, connection pools, TLS, proxy, oauth — see the shared
HTTP config below).
senders: - sondar: repo: app_logs sourcetype: json url: - https://sondar.internal:9200 token: "<collect token>" gzip: true escape_html: true client: timeout: 30sWhy the sender also answers to keta. The shipped collection configs and
every config written against the earlier product open the senders block with
- keta:, and the server still reads that key to work out which index a
config targets. The agent therefore registers the old name as a deprecated
alias — sender.New would otherwise fail with sender type unsupported : keta
and the entire integration catalog would be dead. Do not use keta: in a new
config, and do not “clean up” the alias in the agent: retiring it requires the
server’s KEY_TYPE_KETA resolution to move in the same change, or configs stop
resolving their target index silently.
The shared HTTP client
Section titled “The shared HTTP client”The client: block on sondar and http senders carries the same options:
timeout, idle_conn_timeout, max_idle_conns, max_idle_conns_per_host,
max_conns_per_host, plus inline proxy, TLS, OAuth2 and cookie-auth config.
It is the same httpcommon.ClientConfig both senders embed.
Per-engine examples
Section titled “Per-engine examples”Each destination below was verified against the sender’s types.go; the
required fields are marked. serialization.type (where offered) defaults to
json; use_batch_format toggles a batched (list) envelope for file and HTTP
output.
Elasticsearch
Section titled “Elasticsearch”senders: - elasticsearch: urls: ["http://localhost:9200"] index: "app_logs" # required indexname_time_field: "@timestamp" # route docs to date-suffixed indexes indexname_timezone: "UTC" username: elastic password: changeme timeout: 30sindex is required; indexname_time_field + indexname_timezone switch on
time-based index names (e.g. app_logs-2026.08.13) instead of a fixed index.
enable_gzip, enable_sniffer, id_field (the document _id), es_type
and the connection pool keys (timeout, idle_conn_timeout, max_idle_conn)
complete the set.
senders: - kafka: brokers: ["127.0.0.1:9092"] # required topic: app_logs # required partition_type: hash # hash (default) or robin topic_tag: topic # take the topic from an event field serialization: type: jsonbrokers and topic are required. partition_type is hash by default
(robin for round-robin); topic_tag lets the topic come from a field on
each event. SASL and TLS ride on the inline KafkaConfig.
senders: - http: urls: ["https://collector.example/api"] method: POST username: user password: secret headers: X-Team: ops content_encoding: gzip use_batch_format: false client: timeout: 30sDefaults: content type application/json, content encoding gzip. The url
singular key is deprecated in favour of urls. A non_retryable_statuscodes
list keeps the agent from retrying responses that will never succeed.
Syslog
Section titled “Syslog”senders: - syslog: address: "logs.example:514" # UDP by default appname: myapp severity_code: 6 # informational facility_code: 1 framing: trueaddress is required. The event’s own fields override these defaults: the
sender looks for version, severity_code, facility_code, proc_id,
msg_id, msg, timestamp, sdid, hostname, source, host and
appname on the event and uses them if present. Structured data rides in
sdid/sdids (with separator for the @-style structured-data keys) and
framing/delimiter control the message trailer. TLS config is inline.
Files and object storage
Section titled “Files and object storage”senders: - file: path: /var/spool/sondar/out.log # required use_batch_format: false serialization: type: json
- s3: endpoint: https://s3.example region: us-east-1 bucket: archive access_key_id: AKIA... secret_access_key: "..." rolling_max_count: 10000 rolling_max_size: 1GB rolling_max_duration: 24h compress_type: gzipThe s3 sender rolls by count/size/duration (all three have bounds), compresses
gzip by default, optionally writes an md5 sidecar (md5_check) and a
timestamp_field. file just writes lines to path.
The archive family
Section titled “The archive family”archive_local, archive_s3 and archive_sftp are write-once copies for
retention and compliance — distinct from the file/s3 senders, which keep
writing to the same target. Shared config: field (the file-path field,
default origin), target_dir (required), target_file_path (a template;
empty = keep the original file name), compress (gzip), keep_dir, md5_check,
delete_after_archive.
senders: - archive_local: target_dir: /srv/retention/logs compress: true delete_after_archive: false
- archive_s3: target_dir: logs/raw s3_endpoint: https://s3.example # required s3_region: us-east-1 # required s3_access_key_id: AKIA... # required s3_secret_access_key: "..." # required s3_bucket: retention # required s3_use_path_style: true s3_server_side_encryption: AES256
- archive_sftp: target_dir: /srv/retention sftp_address: "backup.example:22" # required sftp_user: backup # required sftp_private_key_file: /etc/agent/id_ed25519Databases
Section titled “Databases”Each of mysql, postgres, oracle, mssql, dm needs table_name and
dsn, plus the shared database options (max_open_conns default 10, etc.).
The sender maps the event’s columns onto the table’s schema and inserts; it
reads the table’s column list on start (GetColumnSchemas).
senders: - mysql: table_name: app_logs # required dsn: "<user>:<password>@tcp(localhost:3306)/logs?parseTime=true" max_open_conns: 10Writing logs to a relational database is rarely the right call for volume, but it is the usual answer when a downstream report has to join against existing tables.
OpenTelemetry
Section titled “OpenTelemetry”senders: - opentelemetry: endpoint: "http://localhost:4317" traces_enabled: true metrics_enabled: true logs_enabled: true connect_timeout: 30sendpoint/endpoints, the signal toggles, metadata (header pairs) and the
inline gRPC/TLS tuning. Sondar’s own APM ingest is OTLP on 4317, so this is
how an agent forwards traces/metrics/logs to another collector.
OpenMetrics — expose a metrics endpoint
Section titled “OpenMetrics — expose a metrics endpoint”The openmetrics sender is the one sender that does not push. It converts
each event to the OpenMetrics text format and serves it over HTTP for a
scraper (Prometheus) to collect.
senders: - openmetrics: urls: ["http://0.0.0.0:8899/metrics"] headers: Authorization: "Bearer ..." max_idle_conns: 3 timeout: 5sThe conversion requires each event to be shaped a particular way — an event that does not have all three is logged and dropped:
fields— the metric values. Each key becomes a metric family; a key ending_histogramemits the cumulativele-bucketed histogram lines.tags— the labels, rendered askey="value"in the braces. Boolean values become1/0.timestamp— the metric timestamp. Omitted when zero.
A rendered line looks like this (what a scraper actually receives):
# HELP cpu_usage type metrics for module system_tel# TYPE cpu_usage gaugecpu_usage{host="db-1",env="prod"} 63.2 1723500000# HELP http_requests_total type metrics for module apache# TYPE http_requests_total counterhttp_requests_total{method="GET"} 42 1723500000# EOFThe # TYPE line is derived per metric family, booleans render as 1/0, and
the sample ends with the 10-digit Unix timestamp. A batch that produces no
bytes makes the endpoint answer send 0 bytes to collector — the agent logs
that string as a warning, which is the signal that the upstream events were
not shaped with fields/tags/timestamp.
urls is required (this sender uses the same config shape as http, with
headers, max_idle_conns and timeout). The listener is the collector’s
pull side; push happens only when the scraper hits the URL.