Berserk Docs

Writing Queries

Learn how to query your observability data in Berserk

Berserk uses Kusto Query Language (KQL) to query logs, traces, and metrics. If you've used Azure Data Explorer, Azure Monitor, or Microsoft Sentinel, you already know KQL.

A KQL query starts with a dataset name and flows through a pipeline of operators separated by |. Each operator takes the output of the previous step and transforms it.

default
| where severity_text == "ERROR"
| project $time, body, resource.attributes["service.name"]
| tail 20

This query reads from the default dataset, filters to error-level records, selects three columns, and returns the last 20.

If you get an error that default does not exist, try .show tables or bzrk dataset list to see which tables are available.

Datasets

Every query starts with a dataset name. This is the dataset you created when setting up ingestion:

default
| take 10

Filtering with where

The where operator filters rows based on a condition:

default
| where severity_text == "ERROR"

Combine conditions with and and or:

default
| where severity_text == "ERROR" and resource.attributes["service.name"] == "payment"

Use contains for substring matching, or has for whole-word matching (faster):

default
| where body has "timeout"

Filtering by time

Use ago() to filter relative to now:

default
| where $time > ago(1h)
| where severity_text == "ERROR"

Or use between for an absolute range:

default
| where $time between (datetime(2026-03-01) .. datetime(2026-03-10))

Selecting columns with project

Use project to select and rename columns:

default
| where severity_text == "ERROR"
| project timestamp = $time, body, resource.attributes["service.name"]

Use extend to add computed columns while keeping all existing ones:

default
| extend duration_ms = toreal(duration) / 1000

Counting and aggregating with summarize

Count rows:

default
| where severity_text == "ERROR"
| count

Group and aggregate with summarize:

default
| where $time > ago(1h)
| summarize error_count = count() by tostring(resource.attributes["service.name"])

Create time-series buckets with bin():

default
| where $time > ago(24h)
| summarize count() by bin($time, 1h), severity_text

Sorting and limiting

Sort with sort by (or its alias order by):

default
| where severity_text == "ERROR"
| sort by $time desc

Get the top N rows by a value:

default
| top 5 by duration desc

Return an arbitrary sample with take:

default
| take 100

Return the most recent 100 with tail:

default
| tail 100

This is the same as saying | top 100 by $time. The difference between tail and take is that take terminates the query early -- as soon as it has found N values to present to the user, while tail has to use indexes to find the most recent data.

Multiple result tables

Use semicolons to run independent queries in a single request, each producing its own result table:

default | where severity_text == "ERROR" | count;
default | where severity_text == "WARN" | count

Use fork to split a single pipeline into multiple branches:

default
| fork
  Errors=(where severity_text == "ERROR" | count)
  Recent=(take 10)

See fork for details on named branches, supported operators, and shared-scan optimization.

Working with semi-structured data

Observability data often has fields that aren't part of a fixed schema. Berserk stores the full original record in a special $raw column and can automatically resolve unknown field names from it. See $raw and Field Resolution for details.

default
| where attributes.http.status_code >= 500
| project $time, method = attributes.http.method, url = attributes.http.url

Quick reference

OperatorWhat it doesExample
whereFilter rowswhere severity_text == "ERROR"
projectSelect/rename columnsproject $time, body
extendAdd computed columnsextend ms = duration / 1000
summarizeAggregatesummarize count() by tostring(resource.attributes["service.name"])
sort byOrder rowssort by $time desc
topFirst N by a valuetop 10 by duration desc
takeArbitrary N rowstake 100
tailMost recent N rowstail 100
distinctUnique valuesdistinct resource.attributes["service.name"]
countCount rowscount
joinCombine tablesjoin (other) on id
forkMultiple result tablesfork (count) (take 5)
;Independent queriesT | count; T | take 5

Reference

On this page