Caddy writes structured JSON logs by default. This page sets up Vector as aDocumentation Index
Fetch the complete documentation index at: https://docs.logwiz.io/llms.txt
Use this file to discover all available pages before exploring further.
systemd service that tails /var/log/caddy/access.log and /var/log/caddy/error.log, parses each line with parse_json, maps Caddy’s level field to OTel severity, and ships the result to Logwiz’s OTLP endpoint with a Bearer token. Records land in the otel-logs-v0_9 index with service.name: caddy and OpenTelemetry HTTP attributes (http.request.method, url.path, http.response.status_code, client.address, user_agent.original, …) pre-populated.
Setup
Pick the target index and create an ingest token
The default index for OTLP traffic is
otel-logs-v0_9 — see Indexes for its
schema. In Administration → Tokens, click Create Token, pick otel-logs-v0_9, and
copy the token value from the new row. Tokens are scoped to one index — you cannot reuse a
token across indexes.Enable Caddy file logging
Stock Caddy installs log to
journald. Add the following to /etc/caddy/Caddyfile so Caddy
writes JSON to file instead, then reload Caddy. The log block has to be added to every
site whose access log you want shipped — Caddy’s default JSON encoder is what the parser
expects, so do not add a format directive.Install Vector
Install the Vector package for your platform from the official installation
page. Per-distro instructions (Debian/Ubuntu
apt, RHEL/Fedora dnf, container images) are maintained upstream.
Write the Vector config
Save the following at
/etc/vector/vector.yaml. Replace <your-logwiz> with your Logwiz
base URL and <your-ingest-token> with the token you copied in step 1.read_from: end skips existing content on first start, so installing Vector against an
existing access.log does not replay every historical request. Flip it to beginning if
you want a one-time backfill.Grant Vector read access to /var/log/caddy
The default Caddy package creates
/var/log/caddy/ owned by caddy:caddy with mode 750,
so Vector’s vector user cannot read it. Add vector to the caddy group:Restart Vector
active (running) and the most recent log lines should not
contain config-parse or sink-startup errors.Send a test request
Hit any site Caddy is serving so it writes a fresh access-log line:Replace
http://localhost/ with whichever site URL you configured the log block on.What the parsing does
Tworemap transforms run in sequence. The first, parse_caddy, turns each raw JSON line into structured fields and assigns severity. The second, to_otlp, packs those fields into the OTLP wire format that Logwiz’s ingest endpoint expects.
parse_caddy:
- JSON parse —
parse_json(.message)returns Caddy’s structured object;merge!lifts every key (ts,level,logger,msg,request,status,size, …) onto the event. If parsing fails (transitional config, non-JSON output), the line falls through with the raw.messageintact rather than being dropped. - Severity — Caddy’s
levelfield maps directly to OTel severity:debug→ 5/DEBUG,info→ 9/INFO,warn→ 13/WARN,error→ 17/ERROR,panic/fatal→ 21/FATAL. No status-code override; a 5xx access-log entry stays atseverity_text: INFObecause Caddy logs every access at info level. Filter onattributes.http.response.status_codefor triage.
to_otlp assembles the OTLP envelope. service.name is hard-coded to caddy (resource attribute). The if exists(.request) branch is what makes one VRL block handle both access and error logs cleanly: error-log entries (no .request block) get only log.file.path and caddy.logger, while access-log entries get the full HTTP semantic-convention attribute set.
| OTLP key | Source field |
|---|---|
http.request.method | .request.method |
url.path | .request.uri |
server.address | .request.host |
client.address | .request.remote_ip |
client.port | .request.remote_port (parsed to int) |
network.protocol.name | first half of .request.proto split on / |
network.protocol.version | second half of .request.proto |
user_agent.original | first element of .request.headers."User-Agent" |
http.response.status_code | .status |
http.response.body.size | .size |
caddy.logger | .logger (e.g., http.log.access, http.log.error) |
log.file.path | Vector’s .file source attribute |
timeUnixNano is derived from Caddy’s .ts (float seconds since epoch); if the field is missing or unparseable, Vector’s read time is used as a fallback. The final . = { resourceLogs: [...] } reassignment replaces the event entirely, so the OTLP sink sees only the envelope.
Useful searches
Run these in the Logwiz search box againstotel-logs-v0_9.
Every 5xx response Caddy returned, across all sites:
Troubleshooting
permission deniedreading/var/log/caddy/access.log— Vector’svectoruser is not in thecaddygroup. Runsudo usermod -aG caddy vectorand restart Vector. If that’s not available,sudo chmod a+r /var/log/caddy/*.logworks but loosens permissions for every user on the host.- Records arrive but
attributes.http.*are missing — the entry came fromerror.log(no.requestblock), by design. Filter access-log entries withattributes.caddy.logger:http.log.access. bodylooks like raw JSON instead of just the message — the access log is using a non-defaultformatdirective in the Caddyfile. The parser expects Caddy’s default JSON encoder. Either remove theformatdirective or extend theparse_caddyremap to handle the alternate shape.severity_textis alwaysINFOeven for 5xx responses — by design. Caddy emits every HTTP access atlevel: inforegardless of status code; this parser trusts the source. Filter onattributes.http.response.status_code:>=500to surface server errors.401,403,413,415from Logwiz — same response codes as a misconfigured Vector setup of any kind. See Send logs with Vector for full diagnoses.
Related
- Send logs with Vector — generic Vector setup for tailing files on a host.
- OTLP reference — endpoint URL, response codes, body limits.
- Indexes — the
otel-logs-v0_9schema, so you know what you can search.
