# Instructions
1. Query Axiom datasets using Axiom Processing Language (APL). The query must be a valid APL query string.
**Only use this for `events`, `otel.traces`, and similar datasets. Do NOT use for `otel-metrics-v1` datasets — use `queryMetrics()` instead.**
2. ALWAYS understand schema before substantive queries—do not guess column names or types. Prefer `getDatasetFields()` or APL
`| where _time > ago(5m) | getschema` on a narrow window (use dataset names from `listDatasets`); use `take 1` or project specific columns for sample values.
Before you `where` or `summarize` by a field, estimate cardinality on recent data: `| where _time > ago(5m) | summarize count() by <field> | top 10 by count_`.
Avoid `project *` or projecting all fields on very wide datasets unless deliberately mapping shape (see item 5). Skipping probes causes wrong field names, bad types, and expensive re-runs.
3. Keep in mind that there's a maximum row limit of 65000 rows per query.
4. Prefer aggregations over non aggregating queries when possible to reduce the amount of data returned.
5. Be selective in what you project in each query (unless otherwise needed, like for discovering the schema). It's expensive to project all fields.
6. ALWAYS restrict `startTime`/`endTime` to the narrowest window that answers the question—every query scans data and consumes resources.
Prefer the smallest APL per step; widen time or complexity only after probing (item 2).
7. When filtering for a specific term or value, put it on the right field—use `has`/`has_cs`/`contains` there after item 2. See **Avoid `search`** under Query performance rules.
8. **`map[string]` columns (e.g. `attributes`, `attributes.custom`, `resource` in OTel-style data)** — `getDatasetFields` and
in-query `getschema` show the type but **not** the keys inside the map. You must **sample** (`take`, `project` the map
column, or `mv-expand` + `summarize` to list keys) to learn the structure, then use bracket access
(e.g. `['attributes']['http.method']`, `['attributes.custom']['http.response.status_code']`). Do not assume key names
across services or SDK versions.
### Query performance rules
1. **Narrow `startTime`/`endTime`** — These bound how much data is scanned. Do not rely on in-query `_time` filters alone; keep the API window as tight as your question allows.
2. **`_time` first in APL** — When you filter on `_time` in the query text, put `where _time between (...)` before other filters. This keeps extra in-query narrowing fast.
3. **Most selective `where` first** — Axiom does not reorder predicates; put the filter that removes the most rows earliest.
4. **`project` early and narrowly** — Avoid pulling all columns from very wide datasets (expensive payloads; risk of failures on huge rows).
5. **Prefer fast string ops** — Use `_cs` (case-sensitive) variants when possible; prefer `startswith`/`endswith` over `contains` when applicable; `matches regex` only as a last resort.
6. **Use `has`/`has_cs` for unique-looking strings** — IDs, UUIDs, trace IDs, error codes, session tokens. `has` leverages full-text indexes when available and is much faster than `contains` for high-entropy terms. Use `contains` only when you need true substring matching (e.g., partial paths).
7. **Duration literals** — e.g. `duration > 10s`, not manual conversion.
8. **Avoid search** — scans ALL fields. Use `has`/`has_cs`/`contains` on specific fields.
9. **Avoid heavy `parse_json()` in hot paths** — Filter/narrow first when possible.
10. **Avoid pack(*)** — creates dict of ALL fields per row. Use pack with named fields only.
11. Limit results—use take 10 or top 20 instead of default 1000 when exploring.
12. **Field quoting**—quote identifiers with dots/dashes/spaces: ['geo.country']. For map field keys, use index notation: ['attributes.custom']['http.protocol'].
# Examples
Basic:
- Filter: ['logs'] | where ['severity'] == "error" or ['duration'] > 500ms
- Time range: ['logs'] | where ['_time'] > ago(2h) and ['_time'] < now()
- Project rename: ['logs'] | project-rename responseTime=['duration'], path=['url']
Aggregations:
- Count by: ['logs'] | summarize count() by bin(['_time'], 5m), ['status']
- Multiple aggs: ['logs'] | summarize count(), avg(['duration']), max(['duration']), p95=percentile(['duration'], 95) by ['endpoint']
- Dimensional: ['logs'] | summarize dimensional_analysis(['isError'], pack_array(['endpoint'], ['status']))
- Histograms: ['logs'] | summarize histogram(['responseTime'], 100) by ['endpoint']
- Distinct: ['logs'] | summarize dcount(['userId']) by bin_auto(['_time'])
Text matching & Parse:
- Match on known fields (avoid full-row `search`): ['logs'] | where ['message'] has_cs "error" or ['message'] has_cs "exception"
- Parse logs: ['logs'] | parse-kv ['message'] as (duration:long, error:string) with (pair_delimiter=",")
- Regex extract: ['logs'] | extend errorCode = extract("error code ([0-9]+)", 1, ['message'])
- Contains ops: ['logs'] | where ['message'] contains_cs "ERROR" or ['message'] startswith "FATAL"
Data Shaping:
- Extend & Calculate: ['logs'] | extend duration_s = ['duration']/1000, success = ['status'] < 400
- Dynamic: ['logs'] | extend props = parse_json(['properties']) | where ['props.level'] == "error"
- Pack/Unpack: ['logs'] | extend fields = pack("status", ['status'], "duration", ['duration'])
- Arrays: ['logs'] | where ['url'] in ("login", "logout", "home") | where array_length(['tags']) > 0
Advanced:
- Union: union ['logs-app*'] | where ['severity'] == "error"
- Case: ['logs'] | extend level = case(['status'] >= 500, "error", ['status'] >= 400, "warn", "info")
Time Operations:
- Bin & Range: ['logs'] | where ['_time'] between(datetime(2024-01-01)..now())
- Multiple time bins: ['logs'] | summarize count() by bin(['_time'], 1h), bin(['_time'], 1d)
- Time shifts: ['logs'] | extend prev_hour = ['_time'] - 1h
String Operations:
- String funcs: ['logs'] | extend domain = tolower(extract("://([^/]+)", 1, ['url']))
- Concat: ['logs'] | extend full_msg = strcat(['level'], ": ", ['message'])
- Replace: ['logs'] | extend clean_msg = replace_regex("(password=)[^&]*", "\1***", ['message'])
Common Patterns:
- Error analysis: ['logs'] | where ['severity'] == "error" | summarize error_count=count() by ['error_code'], ['service']
- Status codes: ['logs'] | summarize requests=count() by ['status'], bin_auto(['_time']) | where ['status'] >= 500
- Latency tracking: ['logs'] | summarize p50=percentile(['duration'], 50), p90=percentile(['duration'], 90) by ['endpoint']
- User activity: ['logs'] | summarize user_actions=count() by ['userId'], ['action'], bin(['_time'], 1h)