Log Pipeline
The Log Pipeline feature (Observability > Log Pipeline) provides a managed Vector observability pipeline. You pick log sources and configure sinks in the UI; the backend generates vector.toml on the shared data volume and the Vector container picks it up via --watch-config. No manual Vector configuration or container restarts are needed.
The Vector service (timberio/vector:0.58.0-alpine) runs as a sidecar container in the coreX stack. On Kubernetes it runs in the same pod as the API and HAProxy, listening on 127.0.0.1:601. On Docker Compose and Swarm it runs as a separate service listening on vector:601.
How It Works
HAProxy (JSON log line)
│
│ syslog → :601
▼
Vector container (vector.toml on shared volume)
│
├── source: corex (TCP syslog :601)
│ │
│ ▼
│ haproxy_finalize transform
│ (JSON parse, JA4 decode, req_fp decode,
│ unique_id decode, @timestamp, _doc_id)
│ │
│ ├──→ sink: aws_s3 (per-source)
│ ├──→ sink: datadog_logs (per-source)
│ ├──→ sink: elasticsearch (per-source)
│ └──→ sink: ... (one per enabled sink with source=corex)
│
├── source: waf (file tail coraza-spoa.log)
│ │
│ ▼
│ waf_parse_json transform
│ │
│ └──→ sinks with source=waf
│
└── source: mcp (file tail mcp/events.ndjson)
│
▼
mcp_parse_json transform
│
└──→ sinks with source=mcp
The backend uses a 1-to-1 source-per-sink model: each sink has exactly one source (corex, waf, or mcp). Sources are auto-enabled when at least one enabled sink references them — there are no separate source toggles.
Log Sources
| Source | Input | Description |
|---|---|---|
| corex | TCP syslog on :601 | HAProxy streams its JSON access log line. When any enabled sink has source=corex, the backend automatically emits a managed log ring@vector_tcp line in the HAProxy global section so HAProxy forwards its log to Vector. |
| waf | File tail of coraza-spoa.log | Coraza SPOA writes JSON WAF events to /app/data/coraza-spoa.log. Vector tails this file. |
| mcp | File tail of mcp/events.ndjson | MCP gateway writes newline-delimited JSON events (with rotated .1 file). Vector tails this file. |
You don’t enable sources directly. When you create a sink and select source=corex, the corex source is automatically enabled. When you delete or disable the last sink referencing a source, that source stops running. This keeps the Vector config minimal — no idle sources consuming resources.
VRL Transforms
Before events reach sinks, Vector applies VRL (Vector Remap Language) transforms that parse and enrich the raw log lines. These transforms are built-in and run automatically — you don’t configure them.
corex (haproxy_finalize)
The HAProxy JSON log line arrives in the .message field via syslog. The transform:
- Parses JSON —
parse_json(.message)and merges into the event. Non-JSON lines (HAProxy internal messages like SSL handshake errors) are parsed with a regex into structured fields and taggedhaproxy_internal = true. - Null-coerces dash fields — HAProxy sends
-for numeric fields when no value is available (e.g. WAF fields when no WAF rules matched, timing fields for error responses). The transform converts these tonullso structured sinks don’t reject them. - Decodes JA4 — Splits the JA4 fingerprint into
ja4_a,ja4_b,ja4_cand extractsja4_proto,ja4_version,ja4_sni,ja4_cipher_count,ja4_ext_count,ja4_alpn. - Decodes req_fp — Splits the request fingerprint into its subfields (
req_fp_param_keys,req_fp_param_types,req_fp_path_depth,req_fp_hdr_count, etc.). - Decodes unique_id — Splits HAProxy’s unique-id into
unique_id_timestamp,unique_id_pid,unique_id_request_counter,unique_id_client_port. - Flattens WAF match — Extracts
waf_rule_id,waf_action,waf_anomaly_scorefrom the WAF match fields. - Sets
@timestamp— Parses HAProxy’stsfield (format%d/%b/%Y:%H:%M:%S%.3f) into an ISO 8601 timestamp. Falls back tonow()if parsing fails. - Sets
_doc_id— Usesunique_idas the document ID (for Elasticsearch deduplication); generates a UUID if missing. - Tags
corex_source— Adds acorex_sourcefield identifying the originating source.
waf (waf_parse_json)
Parses the Coraza SPOA JSON log line, extracts rule ID, severity, message, client IP, URI, and action.
mcp (mcp_parse_json)
Parses the MCP gateway newline-delimited JSON events.
Sinks
Each sink forwards events from one source to an external destination. You can create multiple sinks per source (e.g. send corex logs to both S3 and Datadog). You can also create the same sink type for different sources (e.g. one aws_s3 sink for corex and another for waf).
AWS S3
Forwards logs to an S3 bucket (or S3-compatible storage like MinIO, Cloudflare R2, Wasabi).
| Option | Required | Secret | Description |
|---|---|---|---|
bucket | Yes | No | S3 bucket name |
region | Yes | No | AWS region (e.g. us-east-1) |
key_prefix | No | No | Key prefix template (e.g. corex/{source}/%Y/%m/%d/) |
compression | No | No | gzip (default), zstd, or none |
endpoint | No | No | Custom endpoint for S3-compatible storage (e.g. https://minio:9000) |
access_key_id | No | Yes | AWS access key (optional with IAM roles) |
secret_access_key | No | Yes | AWS secret key |
session_token | No | Yes | Temporary session token (STS) |
assume_role | No | No | ARN of role to assume |
Azure Log Analytics
Forwards logs to Azure Monitor via the Data Collection Rules (DCR) ingestion endpoint.
| Option | Required | Secret | Description |
|---|---|---|---|
endpoint | Yes | No | Data collection endpoint URL (e.g. https://<dce>.ingest.monitor.azure.com) |
dcr_immutable_id | Yes | No | Data collection rule immutable ID (e.g. dcr-…) |
stream_name | Yes | No | Custom stream name (e.g. Custom-corex) |
azure_credential_kind | No | No | client_secret or managed_identity |
tenant_id | No | No | Azure AD tenant ID (for client_secret auth) |
client_id | No | No | Azure AD client/app ID (for client_secret auth) |
client_secret | No | Yes | Azure AD client secret |
Datadog Logs
Forwards logs to Datadog’s log intake.
| Option | Required | Secret | Description |
|---|---|---|---|
api_key | Yes | Yes | Datadog API key |
site | No | No | Datadog site: datadoghq.com (US1), datadoghq.eu (EU), us3.datadoghq.com (US3), us5.datadoghq.com (US5), ap1.datadoghq.com (AP1) |
endpoint | No | No | Custom intake endpoint (optional; defaults to the site’s intake URL) |
compression | No | No | zstd (default), gzip, or none |
Elasticsearch
Forwards logs to Elasticsearch or OpenSearch.
A ready-to-run OpenSearch + OpenSearch Dashboards deployment for coreX logs is available at github.com/ne4u/corex-logging. It includes index templates, dashboards, and a docker-compose stack pre-configured for the coreX log pipeline.
| Option | Required | Secret | Description |
|---|---|---|---|
endpoints | Yes | No | Comma-separated list of ES endpoints (e.g. https://es1:9200, https://es2:9200) |
index | No | No | Index name pattern (default: corex-log-%Y.%m.%d). Per-source overrides: index_corex, index_waf, index_mcp |
auth_strategy | No | No | none (default), basic, or api_key |
user | No | No | Username (for basic auth) |
password | No | Yes | Password (for basic auth) |
api_key | No | Yes | API key (for api_key auth) |
opensearch_service_type | No | No | "" (Elasticsearch), managed (AWS OpenSearch managed), or serverless (AWS OpenSearch serverless) |
tls_verify_certificate | No | No | Verify TLS certificate (bool) |
tls_verify_hostname | No | No | Verify TLS hostname (bool) |
By default, all sources write to the same daily index pattern (e.g. corex-log-%Y.%m.%d). To send different log categories to different indices, set index_corex, index_waf, and index_mcp on the sink. Alternatively, create separate sinks each with one source and its own index setting.
HTTP
Forwards logs to a generic HTTP endpoint via POST/PUT/PATCH.
| Option | Required | Secret | Description |
|---|---|---|---|
uri | Yes | No | Endpoint URL (e.g. https://logs.example.com/ingest) |
method | No | No | post (default), put, or patch |
encoding | No | No | ndjson (default), json, or text |
compression | No | No | none (default) or gzip |
auth_strategy | No | No | none (default), basic, or bearer |
user | No | No | Username (for basic auth) |
password | No | Yes | Password (for basic auth) |
token | No | Yes | Bearer token (for bearer auth) |
headers | No | No | Custom headers (map format: X-Custom: value, one per line) |
tls_verify_certificate | No | No | Verify TLS certificate (bool) |
New Relic
Forwards logs to New Relic’s log API.
| Option | Required | Secret | Description |
|---|---|---|---|
account_id | Yes | No | New Relic account ID |
license_key | Yes | Yes | New Relic license key (insert key) |
region | No | No | us (default) or eu |
compression | No | No | gzip (default) or none |
Splunk HEC
Forwards logs to Splunk via the HTTP Event Collector.
| Option | Required | Secret | Description |
|---|---|---|---|
endpoint | Yes | No | HEC endpoint URL (e.g. https://splunk-hec:8088) |
token | Yes | Yes | HEC token |
index | No | No | Target Splunk index |
sourcetype | No | No | Splunk sourcetype (default: _json) |
source | No | No | Splunk source field |
host_key | No | No | Event field to use as the host (default: hostname) |
endpoint_target | No | No | event (default) or raw |
tls_verify_certificate | No | No | Verify TLS certificate (bool) |
Secret Handling
Fields marked as secret in the tables above are Fernet-encrypted at rest in the database. The encryption key is derived from VECTOR_SECRETS_KEY (falling back to SECRET_KEY).
- On read — secret fields are masked as
********in API responses, config previews, diffs, and check-sink output - On write — the backend re-encrypts the value; sending
********means “keep the existing secret” (useful when editing a sink without changing credentials) - In backups — secret values are redacted unless the backup is taken with “include secrets” enabled
- At render time — secrets are decrypted and inlined into the generated
vector.toml(Vector needs the plaintext to connect to the sink)
The generated vector.toml on the shared data volume contains decrypted secret values in plaintext (Vector needs them to authenticate). The file is on the haproxy-data volume, which is mounted read-only by the Vector container. Restrict host access to this volume.
Testing and Validation
Validate Config
The Validate button runs vector validate inside the Vector container against the currently-applied vector.toml. This checks TOML syntax and sink configuration without sending data. Returns the validation output (success or error messages).
Test Sink
The Test button (in the sink editor) renders a staging TOML for a candidate sink and runs vector validate inside the Vector container. This exercises real sink healthchecks — it attempts actual connectivity and authentication against the target (S3, Elasticsearch, Datadog, etc.).
With Send test event enabled, it also wires a demo_logs source and runs timeout 15 vector … for end-to-end delivery — a test event is sent through the pipeline to the target sink. The output is secret-redacted.
Returns 503 if the Vector container isn’t reachable.
Preview Config
The Preview button shows the generated vector.toml with secrets redacted (********). Useful for reviewing the full pipeline configuration before applying.
Restart Vector
The Restart button restarts the Vector container (via Docker SDK exec_run or Kubernetes exec). Use this if Vector is in a bad state or after manually editing the config.
Apply Flow
- You create or edit a sink in the UI
- Click Save — the sink is written to the
vector_sinksdatabase table (secrets encrypted) - The unapplied-changes banner appears (config diff compares redacted text so credential changes are detected without leaking secrets)
- Click Apply Changes — the backend:
- Generates
vector.tomlfrom all enabled sinks (secrets decrypted and inlined) - Writes it to the shared data volume
- Writes a
.appliedsnapshot for diff detection - Vector picks up the new config via
--watch-config(no restart needed)
- Generates
Vector’s —watch-config flag detects config file changes and reloads automatically. You only need to manually restart Vector if it’s in a bad state or the config reload fails.
Empty Pipeline
When no sinks are configured, the backend emits a no-op config:
[sources.internal_metrics]
type = "internal_metrics"
[sinks.blackhole]
type = "blackhole"
inputs = ["internal_metrics"]
This keeps Vector running healthily without forwarding anything.
Step-by-Step: Send coreX Logs to Datadog
- Navigate to Observability > Log Pipeline
- Click Add Sink
- Name:
datadog-corex - Type: datadog_logs
- Source: corex
- API key: your Datadog API key
- Site: select your Datadog site (e.g.
datadoghq.comfor US1) - Click Test to verify connectivity (optional: enable Send test event for end-to-end delivery)
- Click Save
- Click Apply Changes in the top banner
- Verify logs appear in Datadog within a few seconds
Step-by-Step: Send WAF Events to Splunk
- Navigate to Observability > Log Pipeline
- Click Add Sink
- Name:
splunk-waf - Type: splunk_hec_logs
- Source: waf
- Endpoint:
https://splunk-hec.example.com:8088 - Token: your HEC token
- Index:
corex-waf(optional) - Sourcetype:
_json(default) - Click Test to verify connectivity
- Click Save
- Click Apply Changes
- Verify WAF events appear in Splunk
Step-by-Step: Send All Sources to Elasticsearch
To send all three sources to the same ES cluster with separate daily indices:
- Create a sink:
- Name:
es-corex, Type: elasticsearch, Source: corex - Endpoints:
https://es1:9200 - Index:
corex-log-%Y.%m.%d - Auth: set as needed
- Name:
- Create a second sink:
- Name:
es-waf, Type: elasticsearch, Source: waf - Endpoints:
https://es1:9200 - Index:
corex-waf-%Y.%m.%d
- Name:
- Create a third sink:
- Name:
es-mcp, Type: elasticsearch, Source: mcp - Endpoints:
https://es1:9200 - Index:
corex-mcp-%Y.%m.%d
- Name:
- Click Apply Changes
Alternatively, create one sink with source=corex and set index_corex, index_waf, and index_mcp overrides — but note this only works if the sink type supports per-source index overrides (Elasticsearch does).
Verification
-
Check pipeline status:
- Navigate to Observability > Log Pipeline
- Verify the sources card shows which sources are active
- Verify the sinks list shows all configured sinks with their enabled status
-
Validate the config:
- Click Validate
- Expect
valid: truewith no error output
-
Test a sink:
- Click Test on a sink
- Enable Send test event for end-to-end delivery
- Expect
ok: truewith no error output
-
Verify delivery:
- Send test traffic through HAProxy
- Check the target sink (S3 bucket, Datadog log explorer, ES index, Splunk search, etc.)
- Verify the log events appear with the expected fields (JA4, req_fp, risk_score, etc.)
-
Check Vector health:
- Click Restart if Vector isn’t picking up config changes
- Verify the pipeline status card shows Vector as reachable
Next Steps
- Metrics & Logging — HAProxy and WAF metrics dashboards
- Audit & Snapshots — Config change tracking
- Architecture — Observability pipeline architecture
- Security Rules — Fields available in log events