Query Syntax
One search language for the logs explorer, live tail, saved searches, export and threshold alert rules. It is the same on TimescaleDB, ClickHouse and MongoDB, so a query you write once returns the same logs on every storage engine.
Overview
A query is a list of clauses separated by whitespace. Every clause must match, so
timeout db finds logs containing both words. Clauses are either free text
(matched against the log message) or key:value field filters.
| Syntax | Meaning |
|---|---|
timeout | The message contains the word timeout (case-insensitive). |
"connection refused" | The message contains this exact phrase (case-insensitive). |
-debug · -"health check" | Exclude logs matching the word or phrase. |
timeout OR refused | Either word. OR must be uppercase. |
service:api | Field equals value. |
-service:api | Field does not equal value. |
level:(error OR critical) | Field is one of the listed values. |
-level:(debug OR info) | Field is none of the listed values. |
user.id:* | Metadata key exists. |
-user.id:* | Metadata key does not exist. |
path:*admin* | Metadata value contains admin (case-insensitive). |
error.message:"disk full" | Quote a field value that contains spaces: key:"value with spaces". |
The logs explorer highlights the query as you type, underlines the offending token when
something is wrong, and autocompletes field names and values. An explicit AND
between two clauses is accepted and means the same as whitespace.
Words and Phrases
- Word - a run of letters, digits and underscores, such as
timeoutoruser_id. It matches whole words in the message, ignoring case.timedoes not matchtimeout. - Phrase - text in double quotes. It matches anywhere in the message as a case-insensitive substring. Use
\"for a quote and\\for a backslash inside a phrase. - Anything else is a phrase too. A term containing punctuation, such as
10:42:13,http://example.com/health,10.0.0.5,Error:or an IPv6 address, is searched as plain text, so you can paste a log line fragment straight into the search bar.
When does key:value count as a field?
Only when the key starts with a letter or underscore, contains only letters, digits,
_, . or -, and is immediately followed by a
non-empty value that does not start with //. Everything else stays free
text, which is why 10:42:13 and http://x need no quoting.
Field Clauses
| Clause | Operator | Matches |
|---|---|---|
key:value | equals | The value, compared as text, is exactly equal. |
-key:value | not equals | Anything else, including logs without the key. |
key:(a OR b) | in | Equal to any value in the list. |
-key:(a OR b) | not in | Equal to none of the values, including logs without the key. |
key:* | exists | The key is present. |
-key:* | not exists | The key is absent. |
key:*text* | contains | The value contains the text, ignoring case. |
Numbers and booleans compare by their text, so status:500 and
ok:false work as you would expect. Arrays and objects have no text form and
never match an equals, in or contains clause. Excluding a contains match
(-path:*admin*) is not supported and is reported as an error.
Negation and Missing Fields
Put - directly in front of a word, phrase or field clause to exclude it. A
negated clause matches logs that do not have the key at all. For example
-env:prod returns staging logs and logs that carry no env field. To
require the key and exclude one value, combine the two clauses:
env:* -env:prod OR Groups
OR (uppercase) joins the two terms next to it into an any-of group. Groups are
AND-ed with the rest of the query, so timeout OR refused db means
(timeout OR refused) AND db. There are no nested parentheses.
- Between free-text terms:
timeout OR "connection reset". - Between values of one field:
env:prod OR env:stagingis the same asenv:(prod OR staging). - OR between clauses of different fields (
service:api OR level:error) is an error with a hint. Run two searches, or use a list on a single field. - OR cannot join excluded terms (
-a OR -b). - Parentheses are only valid as a value list after a key, like
key:(a OR b). Quote text that contains parentheses.
Built-in Fields
| Key | Alias | Supported clauses |
|---|---|---|
level | - | equals, not equals, in, not in. Values: debug, info, warn, error, critical (any case). |
service | - | equals, not equals, in, not in. |
host | hostname | equals, not equals, in, not in. |
trace | trace_id | equals only. |
session | session_id | equals only. |
span | span_id | equals only. |
Any other operator on a built-in field (service:*api*, level:*,
-trace:abc) is a parse error, so the search bar, the API and live tail always
agree. Each built-in can appear once as a positive filter; use a list
(service:(api OR worker)) for several values. Key names are case-sensitive.
Metadata Fields
Any other key filters on the log's metadata. Nested objects are reached with dots:
user.id:42 finds {"user": {"id": 42}}. If a log has a metadata
key that literally contains a dot, such as http.status_code, the exact key wins
over the nested path. Paths never index into arrays.
env:(prod OR staging)
user.id:*
-region:eu-west-1
http.status_code:500
path:*admin* The logs explorer discovers metadata keys from recent logs and offers them as autocomplete suggestions and facets. Clicking a value in the details panel adds or removes the matching clause in the query text, so the query bar is always the full description of the current view.
Time and Scope
Time and project are not part of the query text. They are set by the scope bar at the top of every explore page and carried in the page URL, so a shared link opens with the same scope:
| URL parameter | Meaning |
|---|---|
project | Comma-separated project ids. Missing means all projects you can see. |
range | A preset: 15m, 1h, 6h, 12h, 24h, 48h, 3d, 7d, 14d or 30d. |
from · to | A custom window as ISO 8601 timestamps, used instead of range. |
Through the API, pass projectId, from and to as query
parameters next to q. The last used scope is remembered per browser.
Examples
| Query | Finds |
|---|---|
level:(error OR critical) service:checkout | Errors and criticals from the checkout service. |
"connection refused" -host:web-1 | The phrase on every host except web-1. |
timeout OR deadline db | Logs that mention db and either timeout or deadline. |
user.id:42 -level:debug | Everything user 42 did, without debug noise. |
trace:4bf92f3577b34da6a3ce929d0e0e4736 | All logs of one trace. |
order_id:* -payment:* | Order logs that carry no payment field. |
path:*/admin* -"health check" | Admin paths, excluding health check chatter. |
Errors
Only genuinely malformed input is rejected: an unclosed quote or parenthesis, an
OR without a term on both sides, an OR between different fields, an
unknown level value, or an operator a built-in field does not support. Each error
carries the character position of the problem. The UI underlines that token, and the API
answers HTTP 400. For service:api OR level:error the response is:
{
"error": "OR can only join free-text terms, or values of the same field; use key:(a OR b)",
"position": 12
} API
The query goes in the q parameter. It is accepted by the same endpoints, with
the same authentication as GET /api/v1/logs:
| Endpoint | Purpose |
|---|---|
GET /api/v1/logs | Search logs. limit up to 1,000, cursor pagination. |
GET /api/v1/logs/count | Exact count of matching logs. |
GET /api/v1/logs/histogram | Volume over time, stacked by level. Optional bucketSeconds (1 to 604,800); chosen automatically when omitted. |
GET /api/v1/logs/facets | Top values with counts. fields is a comma list of query keys (default level,service,host), limit defaults to 10 and is at most 50. |
GET /api/v1/logs/fields | Metadata keys seen in recent logs. sampleSize 100 to 5,000, default 1,000. |
GET /api/v1/logs/stream | Live tail over SSE. The full query is applied on the server. |
curl -G http://localhost:8080/api/v1/logs \
-H "X-API-Key: lp_your_api_key" \
--data-urlencode 'projectId=PROJECT_UUID' \
--data-urlencode 'from=2026-10-30T08:00:00Z' \
--data-urlencode 'to=2026-10-30T09:00:00Z' \
--data-urlencode 'q=level:(error OR critical) "connection refused" -host:web-1' Legacy parameters. searchMode=substring treats the whole
q as one phrase, as before. searchMode=fulltext or no
searchMode parses q with this grammar, so existing callers keep
working. Alert rules accept the same syntax in their query field (threshold
rules only); an invalid query is rejected with HTTP 400 and a position.
Limits
| Limit | Value |
|---|---|
| Query length through the API | 2,000 characters |
| Metadata field clauses per query | 10 |
| Values in one list | 50 |
| Length of one value | 1,000 characters |
| Length of a field key | 200 characters |
| Histogram buckets | At most 1,000. A wider range with a small bucketSeconds is rejected with HTTP 400; omit bucketSeconds to let LogTide pick a size. |
| Facet fields per request | 12 |
| Facet values per field | 50 |
| Metadata keys discovered | 500, from the most recent sample of logs |
| Analytics reads (count, histogram, facets, fields) | 30 second time limit on every engine |
Numeric comparisons (status:>=500) and nested parentheses are not supported.
Storage Engines
The grammar, the field rules and the handling of missing keys are identical on all three engines and pinned by a shared conformance suite. The differences are in how free-text words are looked up:
| Engine | Words and phrases |
|---|---|
| TimescaleDB | Words use the existing full-text index with English stemming, so connections also finds connection. Phrases are case-insensitive substring matches. |
| ClickHouse | Whole-word and case-insensitive substring matching, no stemming. |
| MongoDB | Whole-word and case-insensitive substring matching, no stemming. Requires MongoDB 5.2 or later (7.0 recommended). |
English stemming on TimescaleDB is the one documented difference. Quoted phrases are never stemmed. See Storage Engines for engine requirements.