nginx writes plain-text logs by default —Documentation Index
Fetch the complete documentation index at: https://docs.logwiz.io/llms.txt
Use this file to discover all available pages before exploring further.
combined format for access logs and a separate text format for error logs. This page sets up Vector as a systemd service that tails /var/log/nginx/access.log and /var/log/nginx/error.log, parses each line with Vector’s built-in parse_nginx_log, maps the error-log severity 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: nginx 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.Confirm nginx is logging to file
Stock nginx already writes
combined-format access logs to
/var/log/nginx/access.log and error logs to /var/log/nginx/error.log — the wizard
targets those defaults. No nginx.conf changes are required. If you have replaced the
default log_format with a custom one, the parser below will fall back to leaving the
raw line in body; either revert to the default combined format or extend the
parse_nginx remap to handle your custom shape.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/nginx
nginx’s Debian/Ubuntu package owns
/var/log/nginx/ as www-data:adm with mode 640,
so Vector’s vector user cannot read it. The adm group is the read-side convention for
log files on those distros — add vector to it: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 nginx is serving so it writes a fresh access-log line:Replace
http://localhost/ with whichever site URL nginx is configured for.What the parsing does
Tworemap transforms run in sequence. The first, parse_nginx, turns each raw text 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_nginx:
- Format detection —
parse_nginx_log(format: "combined")is tried first; if that errors,parse_nginx_log(format: "error")runs. Vector’s built-in parsers handle escape sequences and optional fields; we chain rather than parse in parallel because every line is unambiguously one or the other andcombinedis overwhelmingly more common. If both fail (customlog_format, malformed line) the event falls through with the raw.messageintact rather than being dropped. nginx_formatdiscriminator —"access"if the parsed object has no.severityfield,"error"otherwise. Lets the OTLP transform branch on a single boolean check instead of re-probing field presence.- Severity — error-log entries map nginx’s
severityfield directly to OTel:debug→ 5/DEBUG,info→ 9/INFO,notice→ 10/NOTICE,warn→ 13/WARN,error→ 17/ERROR,crit→ 19/CRIT,alert→ 21/ALERT,emerg→ 23/EMERG. Access entries default to 9/INFO — the combined format has no severity field, every request is implicitly informational. No status-code override; a 5xx access entry stays at INFO. Filter onattributes.http.response.status_codefor triage.
to_otlp assembles the OTLP envelope. service.name is hard-coded to nginx (resource attribute). The if fmt == "access" branch is what makes one VRL block handle both files cleanly: error-log entries get process.pid, thread.id, nginx.connection_id, and (when present) the upstream/server context fields; access-log entries get the full HTTP semantic-convention attribute set. parse_nginx_log("combined") returns the request line as a single .request field (e.g. "GET /api/foo HTTP/1.1"), so the access branch splits it on whitespace to populate http.request.method, url.path, network.protocol.name, and network.protocol.version separately. Malformed request lines (-, empty) skip those attributes rather than emitting partial data.
| OTLP key | Source field | Format |
|---|---|---|
http.request.method | first whitespace-delimited segment of .request | access |
url.path | second segment of .request | access |
network.protocol.name | third segment of .request, split on /, lowercased | access |
network.protocol.version | third segment of .request, split on / | access |
http.response.status_code | .status | access |
http.response.body.size | .size | access |
client.address | .client | both |
user_agent.original | .agent (skipped if -) | access |
http.request.header.referer | .referer (skipped if -) | access |
enduser.id | .user (skipped if -) | access |
process.pid | .pid | error |
thread.id | .tid | error |
nginx.connection_id | .cid (when present) | error |
server.address | .host (when present) | error |
nginx.server | .server (when present — server block) | error |
nginx.upstream | .upstream (when present) | error |
nginx.format | "access" or "error" discriminator | both |
log.file.path | Vector’s .file source attribute | both |
timeUnixNano is derived from the parsed .timestamp field; parse_nginx_log returns it as a Vector Timestamp value, so we convert with to_unix_timestamp directly. If the field is missing or the conversion fails, 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 nginx returned, across all sites:
Troubleshooting
permission deniedreading/var/log/nginx/access.log— Vector’svectoruser is not in theadmgroup. Runsudo usermod -aG adm vectorand restart Vector. If that’s not available,sudo chmod a+r /var/log/nginx/*.logworks but loosens permissions for every user on the host.- Records arrive but
attributes.http.*are missing — the entry came fromerror.log(no access fields), by design. Filter access-log entries withattributes.nginx.format:access. bodylooks like a raw nginx line instead of a structured message — the access log is using a non-defaultlog_formatdirective innginx.conf. The parser expects nginx’s stock combined format. Either remove the customlog_formator extend theparse_nginxremap to handle the alternate shape.severity_textis alwaysINFOfor access entries even on 5xx — by design. nginx’s combined format has no severity field; 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.
- Send Caddy logs with Vector — sibling page for Caddy.
- OTLP reference — endpoint URL, response codes, body limits.
- Indexes — the
otel-logs-v0_9schema, so you know what you can search.
