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
| Mechanism | Behaviour (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 reload | Every 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 fetch | When the fetch at start fails, Vector exits (code 78). | – |
| Global options | A 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 failures | A 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 env | The 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_logs | build_info{version,revision}, uptime_seconds, component_errors_total{component_id,…}; log events with metadata.level and vector.component_id. | – |
http sink | json codec posts a JSON array of events per batch; auth.strategy = "bearer", request.headers, client TLS; retries 503 with backoff. | – |
vector validate --no-environment | Loads 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-schema | JSON 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/vectorline 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.pemandkey.pem, readable by thevectoruser; the bootstrap and the rendered heartbeat sink name these paths. ca_filein 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.
| Endpoint | Auth | Behaviour |
|---|---|---|
POST /agent/v1/vector/enroll | enrollment token; optional client cert | Creates 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/config | agent 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/heartbeat | agent 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 sectionssources,transforms,sinksandenrichment_tables(spec-format.md §3.2). The component id is the name, else the type, so ids must be unique across sections; ids starting withstrataplaneare reserved. The type comes from the key:options.typeis 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 instancesinks.<type>.<route>, whose component id is the route name. Itsinputsusually 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 (
*) ininputsare 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.outputfor 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 theformattemplate 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@{strataplaneis a render error. rawis TOML written verbatim into the component's own table after its scalar options, with the Telegraf rules (telegraf.md §4); it must not settypeorinputs.- Layer
tagsare a render error: Vector has no global tags; fields are added with aremaptransform. - 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
| Level | Implementation |
|---|---|
| L2 schema | JSON 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 sandbox | vector 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 preview | The 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.
| Situation | What Strataplane shows |
|---|---|
| Served and applied | in_sync as soon as a heartbeat carries the desired artifact. |
Desired not fetched for 2 × pollInterval | out_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.interval | out_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 rejection | out_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 fails | Vector 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 amessage. build_infogives the version (version, raw withrevision); thehosttag the hostname fact;uptime_secondsandcomponent_errors_totalper component id are kept as facts.- Health from the batch: a log entry starting with "An error occurred that
Vector couldn't handle" →
failed; anERRORlog entry →degraded; otherwisehealthy, 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
# instancecomments 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 VERSIONreads the vrl version (crates.io) or revision (git) that Vector'sCargo.lockpins at tagvVERSION, builds the module against it forwasm32-wasip1and writesvector-vrl.wasmand itsinfoanswer totarget/vector-wasm/VERSION, from wheretools/build-bundle/vector.shputs it into the catalog bundle (module namevrl).- The standard library as vrl builds it for wasm32 (
stdlib-baseand 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; theinfoanswer 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_shimin 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:
| Where | Runtime |
|---|---|
| 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-vrl | wasmtime 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).