Skip to content

Logging ​

The router writes one access line per request, and operational messages to the error log. Access lines go to the destinations named by access_log. Each destination has its own bounded queue, so a slow disk or syslog server never delays a request. When a queue is full, the new line is dropped and counted, and the drop count is reported in the error log.

Access log ​

nginx
access_log stdout json;                          # the default when none is given
access_log /var/log/vigilant-heron/access.log main;
access_log syslog:server=192.0.2.10:514,tag=vigilant-heron main;
access_log off;                                  # disables access logs; alone only
  • The destinations are stdout, stderr, a file, or syslog:server=HOST:PORT[,tag=TAG]. Syslog uses UDP and RFC 3164, the local7 facility, and the info severity.
  • The format is json (the default), main, or a name defined with log_format.
  • access_log may repeat. A site with its own access_log lines replaces the top-level ones.
  • Files are reopened on SIGUSR1, so logrotate can move them away.

The built-in formats ​

json writes one JSON object per line, with every value as a string. It has these keys in this order: time, request_id, client_ip, site, host, method, path, query_shape, status, action, reason, rule_id, would_have, attack, ai_instruction, category, decision_error, retry_after, input_tokens and latency_ms.

main writes a text line that starts with the time, the client address, the site and the request line, followed by status, action, reason, would_have, the scores, category, decision_error, retry_after, input_tokens, latency_ms and request_id, as name=value pairs. category, retry_after and request_id are quoted, so a value with spaces or = cannot add a field. A missing value is - in main and an empty string in json.

Log formats ​

A named format is defined at the top level:

nginx
log_format main-ip escape=default "$time_iso8601 $client_ip $site \"$method $path\" $status $action $reason";

The syntax is log_format <name> [escape=default|json] "<template>";.

  • escape default (the default) escapes quotes, backslashes, and every byte outside 0x20 to 0x7e. A value therefore cannot split a line.
  • escape json writes JSON string escapes. Every control character (C0, DEL and the C1 range 0x80 to 0x9f) and U+2028 and U+2029 are written as \uXXXX. Printable non-ASCII text, such as Japanese, is kept as it is.
  • A missing value is - with escape default, and an empty string with escape json.

Variables ​

Each variable is a value of the request or of its decision. A variable may be written as $name or ${name}.

VariableValue
$time_iso8601Time of the request, ISO 8601 with offset
$time_localTime of the request, in the dd/Mon/yyyy:hh:mm:ss +0000 form. The router uses UTC, since it has no timezone database
$msecUnix time of the request, with milliseconds
$request_idValue of the request_id_header, or empty
$rule_idId of the rule that decided the request, or empty
$client_ipClient address after realip selection
$remote_addrTCP peer address
$siteFirst host of the matched site, or empty when no site matched
$hostNormalized host of the request
$methodRequest method
$pathRequest path
$query_shapeNames of the query parameters and the kind of each value, never the value itself: p=<num>&s=<text>. See below
$statusStatus sent to the client
$actionallow, block, redirect or reject, the last for a request refused by a limit
$reasonWhy the decision was made, see below
$would_haveIn shadow mode, the action that would have been taken
$attackAttack score from the backend
$ai_instructionAI-instruction score from the backend
$categoryCategory from the backend, when it gives one
$decision_errorCause of a failed decision, see below
$retry_afterRetry-After value of the backend's rate-limit answer, when it sent one
$input_tokensInput tokens sent to the backend, for backends that count them
$upstream_statusStatus the upstream answered with, or -
$upstream_errorWhy the upstream gave no response: connect_timeout, connect_failed, read_timeout, send_timeout or upstream_failed
$latency_msTime the router took for the request, in milliseconds
$limit_req_statusOutcome of limit_req, see Limits
$limit_conn_statusOutcome of limit_conn, see Limits
$http_<name>A request header, with _ for -

The $reason values are model, below_threshold, rule, skip, skip_rule, access_denied, invalid_host, unknown_host, fail_closed_path, rate_limited_allowed, rate_limited_blocked, client_busy_allowed, client_busy_blocked, decision_error_allowed, decision_error_blocked, and the limit and timer reasons such as limit_req, limit_conn, header_timeout, header_too_large, body_timeout, body_too_large, unreadable_body and uri_too_long.

The $decision_error values are rate_limited, backend_busy, client_busy, timeout, connection, http_status, body, parse, request, invalid_score, rejected and invalid_response. How it works says what each one means.

$http_<name> cannot name a credential. cookie, authorization, proxy-authorization, x-api-key, x-auth-token and every name in redact_header are refused when the configuration loads, so a log line never contains them.

$query_shape ​

$query_shape records the names of the query parameters and the kind of each value, never the value itself. A query ?p=42&s=secret+words is logged as p=<num>&s=<text>. Query strings often carry tokens and email addresses, so the values are left out. The log still shows which kinds of requests a site receives.

  • <num>: 1 to 20 digits.
  • <word>: 1 to 64 letters, digits, _ or -.
  • <text>: any other value.
  • <empty>: a parameter with = and no value.
  • <bare>: a parameter without =. Its text is not logged, since it may be a token.
  • <name>: a parameter name that is not a plain identifier of up to 32 characters (letters, digits, _, ., -, [ and ], starting with a letter or _).

At most 16 parameters are shown, and a further parameter adds <more>. Parameters are separated by & as in the request, and ; is also accepted as a separator. The variable is empty when the request has no query.

Error log ​

nginx
error_log /var/log/vigilant-heron/error.log warn;

The destination is stderr, stdout or a file, and the level is error, warn, info (the default) or debug. Syslog is not available for the error log. The error log is reopened on SIGUSR1 and on SIGHUP. A limit event carries limit_error=zone_exhausted when a zone had no free state.

Released under the MIT License.