Log filter DSL¶
A per-service log { filter = "<expression>" } drops log lines at the source.
The expression is a boolean predicate evaluated against each raw line; when it is true the line is dropped — it reaches neither the streamed output nor alpha's log file.
An empty or absent filter keeps every line.
The filter runs inside alpha, before the line is written or forwarded, so a dropped line costs only the predicate: no string allocation, no JSON marshal, no socket write.
Placement¶
service "go" "app" {
log {
filter = <<-EOT
hasPrefix(line, "\tfrom ") or (contains(line, "level") and severity(line) <= DEBUG)
EOT
}
}
Use an HCL heredoc (<<-EOT … EOT) so the expression's own " and \ are not fought over by HCL string escaping.
Variables¶
| Name | Type | Source |
|---|---|---|
line |
string | the raw log line, without the trailing newline |
stream |
string | "stdout" or "stderr" |
service |
string | the emitting service's name |
line is only valid as the first argument to a function; stream and service are compared directly.
Functions¶
| Call | Meaning | Cost |
|---|---|---|
contains(line, "s") |
s occurs anywhere in the line |
cheap |
hasPrefix(line, "s") |
line begins with s |
cheap |
hasSuffix(line, "s") |
line ends with s |
cheap |
matches(line, "re") |
RE2 regex matches the line | expensive |
json(line, "path") |
value at a gjson path (e.g. http.status, tags.0) |
lazy |
logfmt(line, "key") |
value of key= in a logfmt line |
lazy |
severity(line) |
the line's level as an ordinal (see below) | lazy |
json, logfmt, and severity extract only the referenced field; they never parse the whole line.
Operators¶
Boolean: and, or, not, and parentheses.
Comparison: ==, !=, <, <=, >, >=.
stream, service, logfmt(...), and a json(...) compared to a string accept only == and !=.
severity(...) and a json(...) compared to a number accept all six.
A literal may appear on either side of a comparison.
Severity¶
severity(line) reads a level from the line — JSON level/severity/lvl, logfmt level=, or a bare token — and maps it to an ordinal:
Syslog aliases (err, crit, emerg, …) are recognized.
The uppercase names above are usable as constants, e.g. severity(line) < WARN.
A line with no recognizable level is treated as unknown and is never dropped by a severity(...) comparison.
Evaluation order and cost¶
The compiler reorders each and/or so the cheapest predicate runs first, regardless of how the expression is written; short-circuiting then means an expensive term is reached only for the lines a cheaper term did not already decide.
So severity(line) < WARN and stream == "stdout" runs the free stream check first and scores severity only on stdout lines.
Predicates are pure and total, which is what makes this reordering safe.
Prefer contains/hasPrefix over matches: a regex is by far the most expensive predicate.
Semantics¶
The predicate selects lines to drop.
Express an allow-list by negation: not (severity(line) >= WARN) keeps only warnings and above.
A malformed expression is a configuration error: it is reported by zordon plan/start before the stack comes up, never at runtime.
Examples¶
# drop ruby backtrace frames (cheap, raw-byte)
filter = hasPrefix(line, "\tfrom ") or hasPrefix(line, "/Users/")
# drop debug JSON logs, gated so plain lines never hit the parser
filter = contains(line, "level") and severity(line) <= DEBUG
# drop successful health checks, reaching a nested field lazily
filter = contains(line, "/healthz") and json(line, "http.status") == 200
# keep only errors on stderr
filter = stream == "stderr" and not (json(line, "level") == "error")
See internal/logfilter for the implementation and benchmarks.