Skip to main content

Vector module

Verified against Vector 0.60.0 (2026-10-19) and 0.57.0 with the spike in spikes/vector-delivery, and against the source of vectordotdev/vector master bdd8842 (2026-10-23), unless noted.

1. What stock Vector gives us​

MechanismBehaviour (verified)Since
[provider] type = "http"Vector fetches its whole config with a GET at start, then every poll_interval_secs, with the configured request.headers and User-Agent: Vector/<version> (<target> <revision>). TLS options (ca_file, crt_file, key_file) sit directly under [provider]; client certificates work. No Accept, no If-None-Match, no request timeout. The provider is not in Vector's documentation and holds nothing but the provider block.provider: old; all formats 0.47
Poll and reloadEvery successful fetch reloads, also when nothing changed; unchanged components keep running. The status code is ignored: a body that does not parse, or a config that does not load (unknown option or component type), is dropped silently and the running config stays. An empty body loads as an empty config, fails with No sources defined and is dropped with an error log. A fetch that hangs stalls the poll loop until the connection ends.–
First fetchWhen the fetch at start fails, Vector exits (code 78).–
Global optionsA changed global option (data_dir, timezone, proxy, …) makes Vector refuse the reload on every poll ("Config reload rejected due to non-reloadable global options", component_errors_total{reason="global_options_changed"}) and keep the old config until restarted.–
Start failuresA new component that fails to build is rolled back to the old config. A component that fails once running (a source that cannot bind) stops Vector.–
Secrets and envThe config the provider fetches resolves neither SECRET[…] nor environment variables (interpolate_env is off by default). Bootstrap files are not interpolated either unless Vector runs with --dangerously-allow-env-var-interpolation.env default off: 0.57
internal_metrics, internal_logsbuild_info{version,revision}, uptime_seconds, component_errors_total{component_id,…}; log events with metadata.level and vector.component_id.–
http sinkjson codec posts a JSON array of events per batch; auth.strategy = "bearer", request.headers, client TLS; retries 503 with backoff.–
vector validate --no-environmentLoads a config and compiles its VRL without running components or touching the network. It refuses a config holding only a provider (no sources, no sinks).–
vector generate-schemaJSON Schema of the whole config, all components of the build included.0.36

Minimum supported Vector: 0.57. Agents reporting an older version are flagged unsupported_version and receive no config. Strataplane manages Vector on Linux; enrollment refuses os = "windows".

2. Bootstrap on the host​

# One-time enrollment (installer, Puppet, Ansible):
curl -sf --cert /etc/vector/strataplane/cert.pem --key /etc/vector/strataplane/key.pem \
-H "Authorization: Bearer $ENROLLMENT_TOKEN" -H "Accept: application/toml" \
-d '{"hostname":"web01.example.com"}' \
https://strataplane.example.com:8444/agent/v1/vector/enroll \
> /etc/vector/strataplane.toml
chown root:vector /etc/vector/strataplane.toml && chmod 0640 /etc/vector/strataplane.toml
echo 'VECTOR_CONFIG=/etc/vector/strataplane.toml' >> /etc/default/vector
# plus the drop-in below, then: systemctl daemon-reload && systemctl enable --now vector

The bootstrap holds only the provider, with the per-agent token inline (bootstrap files are not interpolated):

[provider]
type = "http"
url = "https://strataplane.example.com:8444/agent/v1/vector/config"
poll_interval_secs = 60 # Settings vector: pollInterval
crt_file = "/etc/vector/strataplane/cert.pem" # with a bound certificate
key_file = "/etc/vector/strataplane/key.pem"

[provider.request.headers]
Authorization = "Bearer spv_…"

The stock systemd unit runs vector validate before start and reload, which fails for a provider-only config, and gives up after five restarts in ten seconds, which a first fetch failing at boot causes. Enrollment therefore installs /etc/systemd/system/vector.service.d/strataplane.conf:

[Unit]
StartLimitIntervalSec=0

[Service]
ExecStartPre=
ExecReload=
ExecReload=/bin/kill -HUP $MAINPID
RestartSec=10s
StartLimitInterval=0
  • stratactl agent enroll vector (cli.md) writes the bootstrap, the /etc/default/vector line and the drop-in, so installers need no curl logic. The JSON response of the endpoint carries all three.
  • With mTLS, the identity files live at /etc/vector/strataplane/cert.pem and key.pem, readable by the vector user; the bootstrap and the rendered heartbeat sink name these paths.
  • ca_file in the request adds a CA for the agent listener's certificate to the bootstrap.

3. Endpoints​

All on the agent listener. See openapi.yaml, tag agent-vector.

EndpointAuthBehaviour
POST /agent/v1/vector/enrollenrollment token; optional client certCreates or re-binds host and instance; returns the bootstrap (Accept: application/toml) or its fields with token, /etc/default/vector line and drop-in. Certificate binding as for Telegraf (assurance).
GET /agent/v1/vector/configagent token (+ the bound cert, if the instance has one)Rendered TOML of the desired artifact with the caller's token, the artifact hash and the secret values filled in. Records served_artifact when it changes. Before the instance's first render the request waits up to 15 s for it, since Vector exits when its first fetch fails.
POST /agent/v1/vector/heartbeatagent token (+ the bound cert)Accepts the heartbeat pipeline's event batches (§7). 202 on accept; 503 + Retry-After on overload, which the sink retries.

Serving. Artifacts are stored with placeholders, so hosts of a role share one artifact: @{strataplane/token} for the bearer token of the heartbeat sink, an empty X-Strataplane-Artifact header value, and @{strataplane:<secret>} for each secret reference. Serving fills in the token the request carries, the artifact hash and the secret values the delivery rule admits, each escaped as TOML basic-string content; every placeholder sits inside a basic string, so no value can change the config's structure. A secret delivery is audited (channel vector-config) when the instance is first served an artifact carrying it, not on every poll. If a referenced secret is not deliverable the request fails and the agent keeps its config.

Error bodies. Vector ignores the status code, so every error of this endpoint answers with a JSON problem body, which does not parse as TOML; an empty 200 is never sent. A load balancer in front must not answer with an empty body either and should close idle connections after a short timeout: a held connection stalls the provider (§6).

4. Rendering​

The merged tree becomes TOML, deterministically:

# Rendered by Strataplane. Do not edit; changes are made in the spec.
# secrets: logs_token@v2

data_dir = "/var/lib/vector"

# instance sources.file.nginx
[sources.nginx]
type = "file"
include = ["/var/log/nginx/*.log"]

# instance transforms.remap.app_nginx
[transforms.app_nginx]
type = "remap"
inputs = ["nginx"]
source = '''
. = parse_nginx_log!(.message, "combined")
'''

# instance sinks.http.logs-prod
[sinks.logs-prod]
type = "http"
inputs = ["app_nginx"]
uri = "https://logs.example.com/ingest"

[sinks.logs-prod.auth]
strategy = "bearer"
token = "@{strataplane:logs_token}"

Rules:

  • Instance keys are <section>.<type>[.<name>] with the sections sources, transforms, sinks and enrichment_tables (spec-format.md §3.2). The component id is the name, else the type, so ids must be unique across sections; ids starting with strataplane are reserved. The type comes from the key: options.type is a render error.
  • Global options are the instance settings.global, rendered first as top-level keys. Changing one needs a restart (§6).
  • Routes are sinks: a route's output is { plugin = "sinks.<type>", options = { … } } and becomes the instance sinks.<type>.<route>, whose component id is the route name. Its inputs usually hold a wildcard (["prod_*"]), and layers name their last transform to match; a layer can also append to the route's inputs with a merge directive.
  • Inputs. Wildcards (*) in inputs are expanded against the ids of the agent's sources and transforms, the module's own excluded, so the artifact lists every edge. Every input must name a source or transform (id.output for a named output), a wildcard must match one, a transform or sink needs inputs, transforms must not form a cycle, and sources take none: violations are render errors.
  • Order: global options, then enrichment_tables, sources, transforms, sinks, within each by instance key; options lexical, scalars before tables. Strings with newlines (VRL programs) become multi-line literal strings where that is exact.
  • Secret refs become @{strataplane:<key>}, inside the format template if one is given. A comment lists the secret versions the artifact was rendered against (# secrets: logs_token@v2), so a new version changes the artifact. User text containing @{strataplane is a render error.
  • raw is TOML written verbatim into the component's own table after its scalar options, with the Telegraf rules (telegraf.md §4); it must not set type or inputs.
  • Layer tags are a render error: Vector has no global tags; fields are added with a remap transform.
  • An agent without components renders: its config is the heartbeat pipeline alone.

Module-owned heartbeat pipeline​

Rendered last into every artifact. Its values come from the vector Settings object (spec-format.md §3.8):

[sources.strataplane_metrics]
type = "internal_metrics"
scrape_interval_secs = 60 # heartbeat.interval

[transforms.strataplane_metrics_filter]
type = "filter"
inputs = ["strataplane_metrics"]
condition = 'includes(["build_info", "component_errors_total", "uptime_seconds"], .name)'

[sources.strataplane_logs] # absent with heartbeat.logLimit = 0
type = "internal_logs"

[transforms.strataplane_logs_filter]
type = "filter"
inputs = ["strataplane_logs"]
condition = 'includes(["WARN", "ERROR"], .metadata.level)' # heartbeat.logLevel

[transforms.strataplane_logs_throttle]
type = "throttle"
inputs = ["strataplane_logs_filter"]
threshold = 50 # heartbeat.logLimit per interval
window_secs = 60

[sinks.strataplane_heartbeat]
type = "http"
inputs = ["strataplane_metrics_filter", "strataplane_logs_throttle"]
uri = "https://strataplane.example.com:8444/agent/v1/vector/heartbeat"
method = "post"
auth.strategy = "bearer"
auth.token = "@{strataplane/token}"
batch.timeout_secs = 60 # one batch per interval
encoding.codec = "json"
healthcheck.enabled = false
request.headers.X-Strataplane-Artifact = "" # set when served

Applied detection. A heartbeat carrying X-Strataplane-Artifact = H proves the agent loaded the config with artifact H, because the header is only part of that config: applied_artifact is set to H. On a reload the replaced sink flushes its last batch with the old header, and the new one reports H at the end of its first interval.

5. Validation​

LevelImplementation
L2 schemaJSON Schema per component type and Vector version, split from vector generate-schema of that version (tools/README.md), and one for the global options. Each component table is checked with its type and inputs; unknown options are errors, also where Vector itself accepts them silently (verified: demo_logs with an unknown key passes vector validate). The module's strataplane* components are skipped.
L3 sandboxvector validate --no-environment of the matching version (the static binary of the official image), run as a subprocess against the served bytes with a dummy token and dummy secret values, in a network namespace (unshare -rn) where the platform permits one. It loads the config and compiles the VRL of every remap; it reads neither the identity files the heartbeat sink names nor other component files. A non-zero exit fails the check with the report's error blocks. Linux servers only; elsewhere L3 reports itself skipped.
VRL previewThe vector-vrl wasm (§8) for remap programs, against user-supplied events. Informational, not a gate.

Validator binaries and schemas come from the per-version catalog bundle (operations.md §5).

6. Reload failures and sync​

The agent fetches every pollInterval and reports every heartbeat.interval; both come from the vector Settings object, and pollInterval is written into the bootstrap at enrollment.

SituationWhat Strataplane shows
Served and appliedin_sync as soon as a heartbeat carries the desired artifact.
Desired not fetched for 2 × pollIntervalout_of_sync, watcher_stalled: the provider's poll hangs on a held connection or the process is gone. Restarting Vector is left to the operator's tooling.
Fetched, not applied after 2 × pollInterval + heartbeat.intervalout_of_sync, reload_failed: Vector dropped the config (it should not pass L3, but a component can still fail to build on the host) and keeps the old one.
A heartbeat reports the global-options rejectionout_of_sync, restart_required: the agent runs its old config until Vector restarts. The rejection log line of the batch sets it, not the cumulative counter.
A running component failsVector stops; its last heartbeat carries the error logs (health failed), systemd restarts it with the drop-in's delay, and without heartbeats the agent becomes not_reporting.

Secrets never break a config by design. The reconciler renders a secret reference only for an instance the delivery rule can admit (architecture.md §11); otherwise the render fails (render_error) and the agent keeps its config.

7. Heartbeat ingest​

  • The body is a JSON array of Vector events; unknown fields are ignored. Metric events have a name, log events a message.
  • build_info gives the version (version, raw with revision); the host tag the hostname fact; uptime_seconds and component_errors_total per component id are kept as facts.
  • Health from the batch: a log entry starting with "An error occurred that Vector couldn't handle" → failed; an ERROR log entry → degraded; otherwise healthy, plus staleness (agent_health). The error counters are cumulative since start, so they are shown, not judged.
  • Log entries are stored with their level and component id; the id maps back to the instance key through the # instance comments of the artifact the agent runs.

8. VRL preview​

Purpose. Answer "what does this remap program make of these events?" in milliseconds, deterministically, without a Vector sandbox. The assistant iterates on VRL with vector-vrl against sample events (assistant.md §5); the same module serves the CLI and the server.

Build (tools/vector-vrl, Rust, a workspace of its own):

  • build.sh VERSION reads the vrl version (crates.io) or revision (git) that Vector's Cargo.lock pins at tag vVERSION, builds the module against it for wasm32-wasip1 and writes vector-vrl.wasm and its info answer to target/vector-wasm/VERSION, from where tools/build-bundle/vector.sh puts it into the catalog bundle (module name vrl).
  • The standard library as vrl builds it for wasm32 (stdlib-base and the crypto functions): Vector's own functions (get_secret, get_enrichment_table_record, parse_dnstap, …), the environment, system and network functions (get_env_var, get_hostname, http_request, …) and the few that vrl leaves out of wasm builds (dns_lookup, reverse_dns, validate_json_schema) are missing; the info answer lists the 193 functions of the 0.60.0 module. Calling a Vector function is a compile error with a note that it exists only inside Vector.
  • Verified 2026-10-24: Vector 0.60.0 (vrl 0.37.0) and 0.57.0 (vrl 0.33.1 at git 33fec9bd), 6.2 MB, correct output under wasmtime 50 and under @bjorn3/browser_wasi_shim in Node.

ABI. WASI command module, JSON in on stdin, JSON out on stdout, no filesystem, no network. now() reads the wall clock, which every host fixes to the request's default_time. A program runs as remap runs it: the vector metadata is read-only, an event the program aborts is dropped, an event it fails on passes on unchanged with the error. {"abi": 1, "op": "info"} lists the module's functions; a request the module refuses as a whole (unknown timezone) answers with a top-level error:

{ "abi": 1, "program": ". = parse_nginx_log!(.message, \"combined\")\n.seen = now()",
"events": [{ "message": "1.2.3.4 - - [24/Oct/2026:07:00:00 +0000] \"GET / HTTP/1.1\" 200 12 \"-\" \"curl\"" },
{ "message": "garbage" }],
"default_time": "2026-10-24T12:00:00Z", "timezone": "UTC" }
{ "abi": 1, "vector_version": "0.60.0", "vrl_version": "0.37.0",
"compiled": true, "diagnostics": [], "diagnostics_text": "",
"results": [{ "event": {…}, "output": { "client": "1.2.3.4", "status": 200, "seen": "2026-10-24T12:00:00Z", … },
"dropped": false, "error": null },
{ "event": { "message": "garbage" }, "output": { "message": "garbage" }, "dropped": false,
"error": "function call error for \"parse_nginx_log\" at (4:42): failed parsing log line" }] }

A program that does not compile has compiled = false, no results, and its diagnostics both structured (severity, VRL error code, labels with byte offsets, notes) and in diagnostics_text as vector vrl prints them.

Hosts:

WhereRuntime
Browser (assistant shell)The Web Worker of the parser modules with @bjorn3/browser_wasi_shim and a fixed clock; module fetched from /api/v1/catalog/vector/{version}/wasm/vrl, compiled once and cached (IndexedDB). Terminated on timeout.
stratactl vector-vrlwasmtime embedded in the CLI; modules cached locally per version hash.
Server (POST /api/v1/tools/vector/vrl)wasmtime with epoch and memory limits; the bundle of the version, else the nearest older one. For clients without local wasm.

With a fixed default_time, results depend only on (version, sha256(program), sha256(events), timezone).

9. Import​

stratactl import vector PATH turns a Vector config file, or a config directory, into layers for migration: one layer per file at the directory's root and one for its sources/, transforms/, sinks/ and enrichment_tables/ files (the file stem is the component id), with one instance per component keyed <section>.<type>.<id> and the other top-level options as settings.global. Ids that are not [a-z0-9_-] or start with strataplane are renamed, and inputs follow them; api, provider, secret and tests are not imported. TOML, YAML and JSON are read by extension, as Vector does. The result is written into the working copy and goes through a change set like any other edit. The UI's import of a single file uses the same converter (POST /api/v1/tools/vector/import; the file is not stored).