> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sazabi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Query syntax

> The log query spec that logs query execute and the API run, with its predicates, boolean logic, comparators, fields, measures, and time windows, and examples you can paste.

Log search has no free-text query language. You ask in plain language, and Sazabi turns the question into a structured query. That query is a JSON object called the log query spec, and you can write one yourself to run exactly what you mean. This page is the reference for that spec. For asking questions, see [Search logs](https://docs.sazabi.com/explore/search.md).

## Run a spec

Run a spec with the CLI. Pass it as one quoted JSON argument, as `@file.json`, or as `-` to read standard input:

```bash theme={"system"}
npx -y @sazabi/cli@latest logs query execute @query.json --since 6h
```

To start from a query you already asked, save its spec, edit it, and run the edited file:

```bash theme={"system"}
npx -y @sazabi/cli@latest logs query get your-query-id --json | jq .spec > query.json
```

`npx -y @sazabi/cli@latest logs query schema` prints the spec's JSON Schema, for an editor or a validator. A spec the service cannot run is refused with each problem and where it is.

The API takes the same object as `spec` on `POST /v1/logs/queries/execute`; see [Run a log query](https://docs.sazabi.com/reference/api/logs/execute-query.md). Pass either `spec` or `queryId`, not both.

## Shape of a spec

```json theme={"system"}
{
  "version": 2,
  "predicate": { "op": "and", "children": [] },
  "dimensions": [{ "kind": "service" }],
  "measure": { "op": "count" },
  "output": "table"
}
```

| Member | Required | What it does |
| - | - | - |
| `version` | Yes | Always `2`. |
| `predicate` | Yes | Which lines to read. `{"op":"and","children":[]}` reads every line. |
| `dimensions` | Yes | Fields to group by, at most 3. `[]` for none. |
| `measure` | Yes | What to compute over the matching lines. |
| `output` | Yes | `table`, `series`, or `evidence`. |
| `bucket` | No | Time bucket for a `series` output. |
| `series` | No | `limit` and `overflow` for how many groups to keep. |
| `order` | No | How groups are ranked. Needs at least one dimension. |
| `times` | No | `true` adds `firstSeen` and `lastSeen` to each result. |

Unknown members are refused. The time window is not part of the spec; see [Time range](#time-range).

## Predicate

A predicate is a tree. Each node is one of `and`, `or`, `not`, or `compare`.

```json theme={"system"}
{
  "op": "and",
  "children": [
    {
      "op": "compare",
      "field": { "kind": "service" },
      "comparator": "eq",
      "value": "checkout"
    },
    {
      "op": "not",
      "child": {
        "op": "compare",
        "field": { "kind": "message" },
        "comparator": "contains",
        "value": "health check"
      }
    }
  ]
}
```

### Boolean logic

| Node | Shape | Matches |
| - | - | - |
| `and` | `{"op":"and","children":[...]}` | Lines that match every child. With no children, every line. |
| `or` | `{"op":"or","children":[...]}` | Lines that match any child. With no children, no line. |
| `not` | `{"op":"not","child":{...}}` | Lines that do not match the child. |
| `compare` | `{"op":"compare","field":{...},"comparator":"...","value":...}` | Lines where the field satisfies the comparison. |

### Comparators

| Comparator | Value | Matches |
| - | - | - |
| `eq` | One value | The field equals the value, exactly and case-sensitively. |
| `neq` | One value | The field does not equal the value. |
| `in` | A list of up to 100 values | The field equals any value in the list. |
| `gt`, `gte`, `lt`, `lte` | A number | The field, read as a number, is greater than, at least, less than, or at most the value. |
| `contains` | One value | The field contains the text, ignoring case. |
| `starts_with` | One value | The field starts with the text, case-sensitively. |
| `exists` | None | The field is present. |

There are no wildcard or regular-expression operators. Use `contains` for a substring and `starts_with` for a prefix.

Values are strings, numbers, booleans, or `null`, with strings up to 8,192 characters. `exists` takes no `value`; every other comparator requires one, and `in` requires an array. The ordering comparators need a numeric value, and a field that does not read as a number never matches them. `severity` is the exception: it takes level names, covered next.

A predicate can have at most 50 children per node, 8 levels of nesting, 200 nodes, and 65,536 characters of values in total.

## Fields

Every `compare`, dimension, and measure names a field with a `kind`.

| Field | Reads |
| - | - |
| `{"kind":"service"}` | The service that wrote the line. |
| `{"kind":"severity"}` | The line's level: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, lowest first. Sazabi reads the level the sender wrote in any spelling (`err`, `Error`, `WARNING`, `critical`), or its OpenTelemetry severity number when it wrote none. |
| `{"kind":"message"}` | What the line says. For a JSON line, its `msg`, `message`, or a tracing event's `fields.message`; for a text line, the whole line. |
| `{"kind":"body"}` | The whole log line as text. |
| `{"kind":"attribute","source":"log","key":"order.id"}` | An OpenTelemetry attribute. `source` is `log` for the line, `resource` for what wrote it, or `scope` for its instrumentation scope. |
| `{"kind":"body_json","path":["metadata","durationMs"]}` | A value inside a JSON log line, by object path of 1 to 8 keys. |
| `{"kind":"column","name":"http.route"}` | A field every line carries in a fixed place. |

`severity` compares with `eq`, `neq`, `in`, `gt`, `gte`, `lt`, `lte`, and `exists`, and only with level names. `gte` with `"WARN"` means warnings and worse. To match the text the sender wrote, use the `severity_text` column.

The `column` names are:

* `severity_text`, `trace_id`, `span_id`
* `deployment.environment`, `k8s.pod.name`, `event.name`
* `http.request.method`, `http.route`, `url.path`, `http.response.status_code`, `operation_kind`, `server.address`, `http.request.body.size`, `http.response.body.size`

A query that uses any of the `http.*`, `url.path`, `operation_kind`, or `server.address` columns reads request records only, each request once, so other lines a request writes are not counted as requests.

## Measure

`measure` is what to compute over the lines the predicate selects.

| `op` | Extra members | Returns |
| - | - | - |
| `count` | None | The number of matching lines. |
| `event_rate` | None | Matching lines per second. |
| `distinct` | `field` | The number of distinct values of the field. |
| `numeric` | `field`, `parseAs: "float64"`, `aggregate`, `invalidValues` | An aggregate of the field read as a number. `aggregate` is `sum`, `avg`, `min`, `max`, `p50`, `p95`, or `p99`. `invalidValues` is `drop` to skip values that are not numbers, or `error` to fail the query. |
| `request_count` | None | Requests, each once. Reads request records only. |
| `error_count` | None | Failed requests: 5xx responses, failed RPC calls, spans marked as errors, and requests that recorded an error type. Reads request records only. |
| `duration` | `aggregate` | Request latency in milliseconds. `aggregate` is `sum`, `avg`, `max`, `p50`, `p95`, or `p99`. Reads request records only. |
| `ratio` | `numerator` | The share of the counted lines, from 0 to 1, that also match the `numerator` predicate. |
| `recent_rows` | `limit`, optional `fields`, optional `order` | The matching lines themselves. `limit` is 1 to 5,000. `order` is `newest` (the default) or `oldest`. |

## Group, rank, and output

`dimensions` groups results by up to three fields. `output` shapes the result:

* `table` returns one value, or one row per group.
* `series` returns one value per time bucket and group. Set `bucket` to `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `6h`, `12h`, `1d`, or `1w`.
* `evidence` returns the lines themselves. It goes with the `recent_rows` measure, and only with that measure.

Groups are ranked by value, largest first. Set `order` to `{"by":"value","direction":"asc"}` for the smallest first, or `{"by":"dimension","direction":"asc"}` to order by the group's own values, which needs exactly one dimension. `series` keeps the first `limit` groups, up to 10,000, and with `"overflow":"other"` folds the rest into one group, or with `"overflow":"drop"` leaves them out.

`"times": true` adds the first and last matching line's time to each result. It does not go with `recent_rows`, whose lines carry their own times.

## Time range

A spec has no time range. Give the window when you run it:

* `--since` is a relative window ending now: whole minutes, hours, or days, such as `30m`, `6h`, or `7d`, up to `90d`.
* `--from` and `--to` are an absolute window as ISO-8601 times. Give both, with `--from` before `--to`.
* With neither, a spec runs over the last hour. `--since` cannot be combined with `--from` or `--to`.

The API takes `window` as `{"kind":"relative","lookbackSeconds":21600}` or `{"kind":"absolute","from":"...","to":"..."}`. A relative window reaches back at most 90 days, and you can search only the logs your project still keeps; see [Log storage & retention](https://docs.sazabi.com/send-data/storage.md).

`--limit` sets the page size, 1 to 5,000 and 50 by default. Matching lines page with `--cursor`; see [Page through matching lines](https://docs.sazabi.com/explore/search.md#page-through-matching-lines).

## Examples

Each example is a spec file you can run with `npx -y @sazabi/cli@latest logs query execute @query.json --since 6h`.

Errors and worse per service:

```json theme={"system"}
{
  "version": 2,
  "predicate": {
    "op": "compare",
    "field": { "kind": "severity" },
    "comparator": "gte",
    "value": "ERROR"
  },
  "dimensions": [{ "kind": "service" }],
  "measure": { "op": "count" },
  "output": "table"
}
```

The 20 newest checkout lines that carry an `order.id` attribute:

```json theme={"system"}
{
  "version": 2,
  "predicate": {
    "op": "and",
    "children": [
      {
        "op": "compare",
        "field": { "kind": "service" },
        "comparator": "eq",
        "value": "checkout"
      },
      {
        "op": "compare",
        "field": { "kind": "attribute", "source": "log", "key": "order.id" },
        "comparator": "exists"
      }
    ]
  },
  "dimensions": [],
  "measure": { "op": "recent_rows", "limit": 20, "order": "newest" },
  "output": "evidence"
}
```

Errors per service every 15 minutes, for the top five services:

```json theme={"system"}
{
  "version": 2,
  "predicate": {
    "op": "compare",
    "field": { "kind": "severity" },
    "comparator": "in",
    "value": ["ERROR", "FATAL"]
  },
  "dimensions": [{ "kind": "service" }],
  "measure": { "op": "count" },
  "bucket": "15m",
  "output": "series",
  "series": { "limit": 5, "overflow": "other" }
}
```

Average duration from a JSON field, per service:

```json theme={"system"}
{
  "version": 2,
  "predicate": { "op": "and", "children": [] },
  "dimensions": [{ "kind": "service" }],
  "measure": {
    "op": "numeric",
    "field": { "kind": "body_json", "path": ["metadata", "durationMs"] },
    "parseAs": "float64",
    "aggregate": "avg",
    "invalidValues": "drop"
  },
  "output": "table"
}
```

The p95 latency of `api` requests per route, slowest first:

```json theme={"system"}
{
  "version": 2,
  "predicate": {
    "op": "compare",
    "field": { "kind": "service" },
    "comparator": "eq",
    "value": "api"
  },
  "dimensions": [{ "kind": "column", "name": "http.route" }],
  "measure": { "op": "duration", "aggregate": "p95" },
  "output": "table",
  "order": { "by": "value", "direction": "desc" }
}
```

The share of `api` lines that are errors, with when the first and last error happened:

```json theme={"system"}
{
  "version": 2,
  "predicate": {
    "op": "compare",
    "field": { "kind": "service" },
    "comparator": "eq",
    "value": "api"
  },
  "dimensions": [],
  "measure": {
    "op": "ratio",
    "numerator": {
      "op": "compare",
      "field": { "kind": "severity" },
      "comparator": "gte",
      "value": "ERROR"
    }
  },
  "output": "table",
  "times": true
}
```

## Further reading

> [card] [Search logs](https://docs.sazabi.com/explore/search.md)
>
> Ask questions in plain language and rerun the queries behind them.

> [card] [Run a log query](https://docs.sazabi.com/reference/api/logs/execute-query.md)
>
> The API operation that runs a spec.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.