Searching your logs
The query language, the fifteen fields you can search, the limits on a result set, and worked examples you can copy — plus what is deliberately not searchable.
Search is the part of the SIEM that earns its keep on an ordinary Tuesday. Somebody asks whether a departed employee's account has been used since they left, or an auditor asks who accessed a particular system in March, and the answer is one query rather than a week of asking around.
The query language is small on purpose. It has seven operators, two combinators and fifteen searchable fields, and that is the whole surface. A small language can be validated properly, translated to a database query safely, and learned in the time it takes to read this article — which is a better trade for somebody who runs IT alongside four other jobs than an expressive language they use twice a year.
Where you run a query
SIEM → Event Search. Earlier versions of this article told you there was no search box and that you had to call the API; that is no longer true. The screen is a genuine ad-hoc query page, and it has five parts:
- The query box — the same field-based language documented in the rest of this article. Nothing about the syntax has changed.
- A period — the last 15 minutes, hour, 6 hours, 24 hours, 7 days or 30 days, or a custom between two times range when none of those is the window you want.
- A Run button. Nothing is executed until you press it, so you can edit a query without firing off half-written versions of it.
- The searchable fields, as clickable chips. Clicking one inserts a valid clause into the box rather than only naming the field, so you can build a working query without having read the field table below.
- A handful of ready-made example queries, which are the fastest way to see the shape of the language before you write your own.
Results come back as a table, newest first, with every field on every row. That is deliberate: a result table always shows the column whose value matched, so you are never looking at a row wondering why it is there.
There is now a second tab, and a second engine behind it
The shape of a query
field operator value [ AND | OR field operator value ... ]Each term is three parts: a field name, an operator and a value. Terms are joined with AND or OR, case-insensitively — and, && and AND are the same thing, as are or and ||.
Precedence is the same as SQL's: AND binds tighter than OR. So a AND b OR c means (a AND b) OR c. There are no parentheses in the language, so if you need different grouping, run two queries.
| Operator | Also written | Meaning |
|---|---|---|
= | == | Equal to. On text fields this is an exact whole-value match, and it is case-sensitive — unlike contains, which is not. action = Login_Failed will not match login_failed. |
!= | Not equal to. | |
> | Greater than. Useful on severity and timestamp. | |
>= | Greater than or equal to. | |
< | Less than. | |
<= | Less than or equal to. | |
contains | ~ | Contains this literal text, case-insensitively. Wildcard characters in your value are neutralised, so contains "50%" looks for the two characters 50% rather than matching everything. |
Values can be bare tokens or double-quoted strings. Quote the value whenever it contains a space, and quote it out of habit when it contains an @ or a colon. An empty pair of quotes is a real value, not a mistake — see the worked examples. Field names are matched case-insensitively; values are not.
Three things the language deliberately does not have, so you do not spend an afternoon looking for them. There are no parentheses, so (a OR b) AND c cannot be expressed — run two queries. There is no regular-expression, list-membership or field exists operator; the seven above are all there is. And there is no way to escape a double quotation mark inside a value, so a value containing one cannot be searched for directly — use contains on a distinctive fragment either side of it.
The fifteen searchable fields
| Field | Type | What it holds |
|---|---|---|
timestamp | Timestamp | When the event happened, according to the source. |
severity | Number 0-10 | 0 informational, 1 low, 5 medium, 8 high, 10 critical. A number, never a word. |
category | Text | authentication, iam, process, network, email, file, registry, cloud, api or other — plus endpoint.detection and endpoint.response, which come from the endpoint side and are described below. |
action | Text | The specific verb — login_failed, login_success, mfa_failed, logout, user_created, password_reset, role_assigned, api_key_created, process_launch, connection_refused, dns_query, or an API operation name. |
outcome | Text | success, failure or unknown. |
source_ip | Text | Where the event came from. |
dest_ip | Text | Where it was going, where that applies. |
user_id | Text | The principal's name or email address, not an internal identifier. Search for [email protected], not for a UUID. |
actor_type | Text | What kind of principal acted — a user, a service, and so on. |
asset_id | Text | The identifier of the machine or device the event is about. For most sources that is the hostname or address as the source reported it. For endpoint events from the Qorionix sensor (source_module = "xdr") it is the sensor's own id — the value that joins to the Endpoints page — and the hostname is shown next to every result in the hostname column, which is a display label rather than a searchable field: filter by asset_id, read the hostname. Note the detection rules call this same value host; search calls it asset_id. |
classification | Text | privileged_audit, detection, infrastructure or bulk_telemetry — a rough statement of how important the event class is. |
source_module | Text | Which part of the platform wrote the event. Events arriving through log collection carry siem-ingest; events from the endpoint sensors carry xdr. |
raw_log | Text | The original line, as sent. Use contains on this. |
region, cell | Text | Deployment location fields. Currently written as empty for every collected event, so the only value that matches is "". They are searchable because the column exists, not because there is anything in them. |
Endpoint events, and how much of them is here
Search is no longer only about collected logs. Two categories of event now flow in from the endpoint (XDR) side, and both carry source_module = xdr.
- `category = endpoint.detection`
- Every detection or alert the endpoint sensors raise. These also carry
classification = detection, soclassification = detection AND source_module = xdris the idiom for everything the endpoint side thought was worth flagging. - `category = endpoint.response`
- Every response action taken on an endpoint: isolate a machine, un-isolate it, kill a process, quarantine a file, restore a file, fetch a file, open a remote shell. The
actionfield carries the response verb itself, so you can search for one kind of action rather than all of them. Response actions describes what each one does.
Worked examples
These are meant to be copied and edited. Every one of them parses.
# Failed sign-ins from one address
category = authentication AND outcome = failure AND source_ip = "203.0.113.5"
# Everything one account did, since a date
user_id = "[email protected]" AND timestamp >= 2026-08-25
# High and critical events only, in a specific window
severity >= 8 AND timestamp >= "2026-08-31 09:00:00" AND timestamp < "2026-08-31 18:00:00"
# Two kinds of failure at once — note AND binds tighter than OR
action = login_failed OR action = mfa_failed
# Anything mentioning a process name, anywhere in the original line
raw_log contains lsass.exe
# Identity changes that were not ordinary successes
category = iam AND outcome != success
# Events with no recorded outcome at all
outcome = ""
# One machine, high severity, last seven days (adjust the date)
asset_id = "LAPTOP-14" AND severity >= 8 AND timestamp >= 2026-08-25
# Everything, newest first — the idiom for 'just show me anything'
severity >= 0That last-but-one example is the one people do not guess. Most of these columns cannot be null, so nothing was recorded here is expressed as an empty string, and outcome = "" is how you ask for it.
Accepted timestamp formats, so you do not have to guess: 2026-08-31, 2026-08-31 14:00, 2026-08-31 14:00:00, 2026-08-31T14:00:00, and the full RFC 3339 form 2026-08-31T14:00:00Z with or without fractional seconds. Anything containing a space must be quoted, or it will tokenise as two terms.
Limits on a result set
- Results default to 100 rows and are always ordered newest first.
- The maximum is 1,000. Be aware of the exact behaviour if you exceed it: a limit above 1,000 does not clamp to 1,000 and does not raise an error — it silently resets to 100. If you asked for 5,000 rows and got 100, that is why.
- There is no pagination. To see older events you narrow the query with
timestamp <rather than turning a page. A query with no time bound searches as far back as your retention entitlement reaches, newest first, up to the row limit. - There is a time-range parameter, and it composes rather than overrides. The request body accepts
start_timeandend_timeas RFC 3339 values, and they are folded into every clause of your query as additionaltimestampconditions. So a range parameter and atimestampterm in the query do not fight — you get the intersection. A malformed value is rejected with an example:start_time must be RFC 3339, e.g. 2026-09-01T00:00:00Z. - The reported total is the number of rows returned, not the number that matched. If a query returns its full limit, read the count as at least this many. There is no match count in the response.
- All fifteen fields come back on every row, not just the ones you filtered on, so a result table always shows the column whose value matched.
Running a search requires the siem:queries.read permission, which is deliberately not granted to everyone by default. A search reads the whole of your organisation's security telemetry — sign-ins, process command lines, file paths — and that is not something every member of staff should be able to do. Saving a search requires siem:queries.write.
When a query is rejected
A bad query is answered with a specific message rather than a generic failure, because the server broke is the wrong thing to tell somebody whose query was simply wrong. The messages you are most likely to see, verbatim:
unknown field "username" (queryable: action, actor_type, asset_id, category, cell,
classification, dest_ip, outcome, raw_log, region, severity, source_ip,
source_module, timestamp, user_id)
"severity" takes a whole number in -32768..32767, got "medium" — severity is 0
(informational) to 10 (critical); quoting a word like "high" will not match
"severity" cannot use `contains`: it is a number, not text — use =, !=, >, >= , < or <=
"timestamp" takes a timestamp, got "yesterday" — try 2026-08-31 or 2026-08-31T14:00:00Z
incomplete expression near "outcome =" — each term is `field op value`, e.g. severity >= 5
unterminated string literalTwo of those are worth a sentence each. severity = "medium" is the single most common mistake and the message spells out the fix, because severity is stored as a number so that it sorts and compares consistently across the whole platform. And unterminated string literal almost always means an apostrophe or a stray quotation mark inside a pasted value.
Saving a search
A search can be saved with a name and a description, and the query is parsed before it is stored, so an unparseable query cannot be saved and then fail later. The saved list is shared across your organisation, and names are unique within it, so two people cannot both own Failed logins. Save the queries you rebuild from memory: the ones you run at month end, the ones an auditor asks for, and the one you use to check whether a departing employee's account is still being used.
You can now delete one. The delete control asks you to confirm, and the confirmation is worth reading rather than dismissing: the saved search is removed for everyone in your organisation, not just for you, because the list is shared. Your events are untouched — deleting a saved search deletes a stored question, never any data.
One further limit worth knowing about, because it explains an occasional slow query rather than a wrong result. There is provision for a second, cheaper storage tier for older events, and the path that would read it currently falls back to querying the ordinary store instead. Your results are correct; nothing is being read from cheap storage, because nothing is being written there. Log retention covers what that means for how long your history actually lasts.
What to read next
- How your logs are normalised — what the values in these fields mean, and where they came from.
- Log retention — how far back a search can reach.
- Dashboards — the same query language, rendered as a chart.
- The pipe query language — the second engine behind the Query tab, in full.