Logstreem
Tabular operators

Tabular operators

Every operator that takes rows in and gives rows out.

Tabular operators are the things that follow a |. They compose in any order that makes sense.

OperatorDoesExample
whereKeeps matching rows| where status >= 500
extendAdds a computed column| extend slow = latency > 1000
projectKeeps only these columns, in this order| project _time, service, latency
project-awayDrops these columns, keeps the rest| project-away message, region
summarizeAggregates, optionally grouped| summarize count() by service
distinctUnique combinations of these columns| distinct service, region
order by / sort bySorts, desc by default| order by latency desc
toporder by and take in one| top 10 by latency desc
take / limitFirst N rows, unordered unless you sorted| take 100
countCollapses to a single count row| count
joinJoins another dataset on a key| join ['deploys'] on service
parsePulls fields out of a string| parse message with "pool " size "/" cap
renderHints the chart type for the console| render timechart

where

Comparisons are == != > >= < <=, plus =~ for case-insensitive string equality. Combine with and / or. Membership is in (…) and its negation !in (…).

LSQL
read top to bottom
1['api-gateway-prod']
2| where level in ("error", "warn")
3| where service != "health-check"
4| where route startswith "/v1/"
5| take 20

Two wheres or one and?

Identical in cost — the planner folds consecutive where lines together. Use whichever reads better; separate lines are easier to comment out while debugging.

summarize

summarize collapses rows into one row per group. Without by you get exactly one row. Aliases are optional but make the output readable.

LSQL
read top to bottom
1['api-gateway-prod']
2| summarize
3 requests = count(),
4 errors = countif(status >= 500),
5 p50 = percentile(latency, 50),
6 p95 = percentile(latency, 95)
7 by service
8| extend error_rate = round(100.0 * errors / requests, 2)
9| order by error_rate desc

join

Joins a second dataset on a shared key. The left side should be the smaller one — usually the aggregate you just built.

LSQL
read top to bottom
1['deploys']
2| where _time > ago(1d)
3| project service, version, deployed_at = _time
4| join kind=inner ['api-gateway-prod'] on service
5| summarize errors = countif(status >= 500) by service, version
6| order by errors desc
kind=Keeps
innerRows present on both sides (default)
leftouterAll left rows; right columns null when unmatched
leftantiLeft rows with no match on the right — the "what is missing" join

leftanti is the one you will reach for

"Which services deployed today have no errors" and "which orders never got a confirmation" are both leftanti. It is the join people forget exists and then hand-roll badly.