Filter language
A Filter is the condition a Log record must meet. The same Filter selects records in a Query and in live tail. A Query is a Filter plus a time range, run against stored Log records; live tail has a Filter but no time range.
A Filter is a line of text. Words are ANDed, and everything below is a way to say more.
Fields
A Filter tests a Field of a Log record. A Field is either a Named field or an Attribute.
The Named fields are:
| Field | What it holds |
|---|---|
service | The Service that wrote the record |
host | The Host it came from |
source | The Source's slug |
container | The container name, for container logs |
level | The Level: trace, debug, info, warn, error or fatal |
body | The Body, the text as it was written |
compose.project, compose.service | Docker Compose names |
kamal.service, kamal.role, kamal.destination | Kamal names |
Any other name is an Attribute: a value the record or its Resource carries, such as status or user_id. When both carry the same name, the record's own value is used. A bare name means the Named field if there is one, and the Attribute otherwise. Write @name to force the Attribute, for example @host:db1 for an Attribute called host, and @"odd key" for a name with spaces.
A record whose Level is unknown has no Level. It is not treated as info.
Matching a value
text
service:api
source:api
level:error
status:500
url:/api/v1field:value matches exactly, and is case-sensitive. url:/api/v1 is an exact match of that path, not a regular expression (see below). Put a value in double quotes when it has spaces or special characters:
text
body:"connection refused"
@"http.route":"/orders/{id}"Inside quotes, \" is a quote and \\ is a backslash. There are no other escapes.
Wildcards
* in a value matches any run of characters, including none. The match is case-insensitive and covers the whole value:
text
host:web-*
service:*-worker* is not allowed on level.
Has a value
field:* matches records where the Field is present and not empty:
text
user_id:*
level:*The second finds records that have a Level at all.
Several values
A list in parentheses, joined by OR, matches any of them:
text
service:(api OR worker)
level:(warn OR error OR fatal)A list takes values joined by OR only.
Levels and numbers
Levels are ordered: trace < debug < info < warn < error < fatal. Compare them with >, >=, < and <=:
text
level:>=warn
level:<infoThe same operators compare numeric Attributes:
text
status:>=500
duration_ms:>1000A value counts as a number only if it is plain digits, optionally with a minus sign, a decimal part and an exponent (-12, 0.5, 1e3). Anything else, such as "", 0x10 or a value with spaces, does not match. There are no units. status:500 is a string match and does not match "500.0".
Free text
A word or a quoted phrase with no Field searches every value of the record: the Body, the Named fields and the Attribute values. It does not search Attribute names or level.
text
timeout
"connection refused"
timeout retryIt matches case-insensitive substrings. Several words are ANDed over the whole record, so timeout retry needs both somewhere in it. A quoted phrase must sit inside one value, literally. To search the Body only, use body:.
AND, OR and NOT
AND is implicit, and can be written. OR must be uppercase. - or NOT negates. NOT binds tighter than AND, and AND tighter than OR. Parentheses group.
text
service:api level:error
service:api AND level:error
service:api OR service:worker
-level:debug
NOT level:debug
source:api (level:error OR level:fatal) -timeout- must touch what it negates: - a is an error. A negation matches a record that lacks the Field, too: -level:debug also finds records with no Level.
Regular expressions
A term between slashes is a regular expression, in RE2 syntax. It is unanchored, case-sensitive unless it starts with (?i), and . matches line breaks. Write it on a Field, or bare to test every value:
text
/timeout \d+ms/
body:/(?i)panic|fatal error/
path:/^\/api\/v[0-9]+\//A term that starts with / is a regular expression only when another / closes it and the term ends there, so /var/log/syslog is free text. Write \/ for a slash inside the expression. Regular expressions are not allowed on level, and one is capped at 1 KB.
Limits
- At most 100 conditions in one Filter; each value in a list counts as one.
AND,ORandNOTnest at most 10 deep. Parentheses and negations in the text nest at most 64 deep.
Errors and warnings
A Filter that could never match, or is malformed, is an error, and the Query does not run. The error shows where in the text it is. These are errors:
text
level:eror
level:>fatall
service:
foo:"bar"bazA Filter that runs but probably isn't what you meant gets a warning: a name or value that no recent Log record has (with a suggestion), an unknown Source slug, a standalone lowercase or, and or not, and a word such as 12:30 that looks like free text but reads as a Field.
text
service:api or service:workerThe second one is a search for records with or in them, not an OR. Write OR in capitals.